feat: Add vim mode to script editor

This adds an option to turn on vim mode using the `monaco-vim` library.
This commit is contained in:
Billy Vong 2021-12-17 12:34:00 -05:00
parent 3436873373
commit fb5d374279
7 changed files with 78 additions and 9 deletions

7
dist/ext/monaco-vim.js vendored Normal file

File diff suppressed because one or more lines are too long

@ -17,12 +17,13 @@
<link rel="stylesheet" data-name="vs/editor/editor.main" href="dist/ext/monaco-editor/min/vs/editor/editor.main.css"/>
<script>
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs" } };
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs", "monaco-vim": "dist/ext/monaco-vim" } };
</script>
<script src="dist/ext/monaco-editor/min/vs/loader.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="dist/ext/monaco-vim.js"></script>
<!-- Google Analytics -->
<script>

@ -2,4 +2,5 @@ export interface Options {
theme: string;
insertSpaces: boolean;
fontSize: number;
vim: boolean;
}

@ -21,12 +21,14 @@ export function OptionsModal(props: IProps): React.ReactElement {
const [theme, setTheme] = useState(props.options.theme);
const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces);
const [fontSize, setFontSize] = useState(props.options.fontSize);
const [vim, setVim] = useState(props.options.vim);
function save(): void {
props.save({
theme: theme,
insertSpaces: insertSpaces,
fontSize: fontSize,
theme,
insertSpaces,
fontSize,
vim,
});
props.onClose();
}
@ -54,6 +56,12 @@ export function OptionsModal(props: IProps): React.ReactElement {
<Typography>Use whitespace over tabs: </Typography>
<Switch onChange={(event) => setInsertSpaces(event.target.checked)} checked={insertSpaces} />
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>Enable vim mode: </Typography>
<Switch onChange={(event) => setVim(event.target.checked)} checked={vim} />
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<TextField type="number" label="Font size" value={fontSize} onChange={onFontChange} />
</Box>

@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef, useMemo } from "react";
import Editor from "@monaco-editor/react";
import * as monaco from "monaco-editor";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
import { OptionsModal } from "./OptionsModal";
import { Options } from "./Options";
@ -86,9 +87,13 @@ let lastFilename = "";
let lastCode = "";
let hostname = "";
let lastPosition: monaco.Position | null = null;
// let vimEditor: any | null = null;
export function Root(props: IProps): React.ReactElement {
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const vimStatusRef = useRef<HTMLElement>(null);
const [vimEditor, setVimEditor] = useState<any>(null);
const [editor, setEditor] = useState<IStandaloneCodeEditor|null>(null);
const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename);
const [code, setCode] = useState<string>(props.filename ? props.code : lastCode);
const [decorations, setDecorations] = useState<string[]>([]);
@ -103,6 +108,7 @@ export function Root(props: IProps): React.ReactElement {
theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces,
fontSize: Settings.MonacoFontSize,
vim: Settings.MonacoVim,
});
const debouncedSetRAM = useMemo(
@ -314,7 +320,39 @@ export function Root(props: IProps): React.ReactElement {
return () => document.removeEventListener("keydown", maybeSave);
});
useEffect(() => {
// setup monaco-vim
if (options.vim && editor && !vimEditor) {
try {
// This library is not typed
// @ts-expect-error
window.require(["monaco-vim"], function (MonacoVim: any) {
setVimEditor(MonacoVim.initVimMode(editor, vimStatusRef.current));
MonacoVim.VimMode.Vim.defineEx('write', 'w', function() {
// your own implementation on what you want to do when :w is pressed
save();
});
MonacoVim.VimMode.Vim.defineEx('quit', 'q', function() {
save();
});
editor.focus();
});
} catch {}
} else if (!options.vim) {
// Whem vim mode is disabled
vimEditor?.dispose();
setVimEditor(null);
}
return () => {
vimEditor?.dispose();
}
}, [ options, editorRef, editor, vimEditor])
function onMount(editor: IStandaloneCodeEditor): void {
// Required when switching between site navigation (e.g. from Script Editor -> Terminal and back)
// the `useEffect()` for vim mode is called before editor is mounted.
setEditor(editor);
editorRef.current = editor;
if (editorRef.current === null) return;
const position = CursorPositions.getCursor(filename);
@ -328,6 +366,7 @@ export function Root(props: IProps): React.ReactElement {
lineNumber: lastPosition.lineNumber,
column: lastPosition.column + 1,
});
editorRef.current.focus();
}
@ -368,9 +407,13 @@ export function Root(props: IProps): React.ReactElement {
monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts");
loadThemes(monaco);
}
// 370px 71%, 725px 85.1%, 1085px 90%, 1300px 91.7%
// fuck around in desmos until you find a function
const p = 11000 / -window.innerHeight + 100;
// TODO: Make this responsive to window resizes
// Toolbars are roughly 108px + vim bar 34px
// Get percentage of space that toolbars represent and the rest should be the
// editor
const editorHeight = 100 - (((108 + (options.vim ? 34 : 0)) / window.innerHeight) * 100);
return (
<>
<Box display="flex" flexDirection="row" alignItems="center">
@ -393,13 +436,16 @@ export function Root(props: IProps): React.ReactElement {
beforeMount={beforeMount}
onMount={onMount}
loading={<Typography>Loading script editor!</Typography>}
height={p + "%"}
height={`${editorHeight}%`}
defaultLanguage="javascript"
defaultValue={code}
onChange={updateCode}
theme={options.theme}
options={{ ...options, glyphMargin: true }}
/>
<Box ref={vimStatusRef} className="monaco-editor" display="flex" flexDirection="row" sx={{ p: 1 }} alignItems="center"></Box>
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
<Button onClick={beautify}>Beautify</Button>
<Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
@ -425,12 +471,14 @@ export function Root(props: IProps): React.ReactElement {
theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces,
fontSize: Settings.MonacoFontSize,
vim: Settings.MonacoVim,
}}
save={(options: Options) => {
setOptions(options);
Settings.MonacoTheme = options.theme;
Settings.MonacoInsertSpaces = options.insertSpaces;
Settings.MonacoFontSize = options.fontSize;
Settings.MonacoVim = options.vim;
}}
/>
</>

@ -163,6 +163,8 @@ interface ISettings extends IDefaultSettings {
MonacoInsertSpaces: boolean;
MonacoFontSize: number;
MonacoVim: boolean;
}
export const defaultSettings: IDefaultSettings = {
@ -254,6 +256,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MonacoTheme: "monokai",
MonacoInsertSpaces: false,
MonacoFontSize: 20,
MonacoVim: false,
theme: {
primarylight: defaultSettings.theme.primarylight,

@ -21,12 +21,13 @@
/>
<script>
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs" } };
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs", "monaco-vim": "dist/ext/monaco-vim" } };
</script>
<script src="dist/ext/monaco-editor/min/vs/loader.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="dist/ext/monaco-vim.js"></script>
<!-- Google Analytics -->
<script>