diff --git a/ui/frontend/editor/MonacoEditorCore.tsx b/ui/frontend/editor/MonacoEditorCore.tsx index 82f0bb90..46530c0a 100644 --- a/ui/frontend/editor/MonacoEditorCore.tsx +++ b/ui/frontend/editor/MonacoEditorCore.tsx @@ -73,6 +73,7 @@ const MonacoEditorCore: React.FC = (props) => { fontFamily: nodeStyle.fontFamily, automaticLayout: true, 'semanticHighlighting.enabled': true, + autoClosingOvertype: 'always', }); setEditor(editor); @@ -81,93 +82,122 @@ const MonacoEditorCore: React.FC = (props) => { editor.focus(); }, []); - useEditorProp(editor, props.onEditCode, (_editor, model, onEditCode) => { - model.onDidChangeContent(() => { - onEditCode(model.getValue()); - }); - }); - - useEditorProp(editor, props.execute, (editor, _model, execute) => { - editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { - execute(); - }); - // Ace's Vim mode runs code with :w, so let's do the same - editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { - execute(); - }); - }); + useEditorProp( + editor, + props.onEditCode, + useCallback((_editor, model, onEditCode) => { + model.onDidChangeContent(() => { + onEditCode(model.getValue()); + }); + }, []), + ); - useEditorProp(editor, props.code, (editor, model, code) => { - // Short-circuit if nothing interesting to change. - if (code === model.getValue()) { - return; - } + useEditorProp( + editor, + props.execute, + useCallback((editor, _model, execute) => { + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { + execute(); + }); + // Ace's Vim mode runs code with :w, so let's do the same + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { + execute(); + }); + }, []), + ); - editor.executeEdits('redux', [ - { - text: code, - range: model.getFullModelRange(), - }, - ]); - }); + useEditorProp( + editor, + props.code, + useCallback((editor, model, code) => { + // Short-circuit if nothing interesting to change. + if (code === model.getValue()) { + return; + } + + editor.executeEdits('redux', [ + { + text: code, + range: model.getFullModelRange(), + }, + ]); + }, []), + ); - useEditorProp(editor, theme, (editor, _model, theme) => { - editor.updateOptions({ theme }); - }); + useEditorProp( + editor, + theme, + useCallback((editor, _model, theme) => { + editor.updateOptions({ theme }); + }, []), + ); const autocompleteProps = useMemo( () => ({ autocompleteOnUse, crates: props.crates }), [autocompleteOnUse, props.crates], ); - useEditorProp(editor, autocompleteProps, (_editor, _model, { autocompleteOnUse, crates }) => { - completionProvider.current = monaco.languages.registerCompletionItemProvider('rust', { - triggerCharacters: [' '], - - provideCompletionItems(model, position, _context, _token) { - const word = model.getWordUntilPosition(position); - - function wordBefore( - word: monaco.editor.IWordAtPosition, - ): monaco.editor.IWordAtPosition | null { - const prevPos = { lineNumber: position.lineNumber, column: word.startColumn - 1 }; - return model.getWordAtPosition(prevPos); - } - - const preWord = wordBefore(word); - const prePreWord = preWord && wordBefore(preWord); - - const oldStyle = prePreWord?.word === 'extern' && preWord?.word === 'crate'; - const newStyle = autocompleteOnUse && preWord?.word === 'use'; - - const triggerPrefix = oldStyle || newStyle; - - if (!triggerPrefix) { - return { suggestions: [] }; - } - - const range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: word.startColumn, - endColumn: word.endColumn, - }; - - const suggestions = crates.map(({ name, version, id }) => ({ - kind: monaco.languages.CompletionItemKind.Module, - label: `${name} (${version})`, - insertText: `${id}; // ${version}`, - range, - })); - - return { suggestions }; - }, - }); + useEditorProp( + editor, + autocompleteProps, + useCallback((_editor, _model, { autocompleteOnUse, crates }) => { + completionProvider.current = monaco.languages.registerCompletionItemProvider('rust', { + triggerCharacters: [' '], + + provideCompletionItems(model, position, _context, _token) { + const word = model.getWordUntilPosition(position); + + function wordBefore( + word: monaco.editor.IWordAtPosition, + ): monaco.editor.IWordAtPosition | null { + const prevPos = { lineNumber: position.lineNumber, column: word.startColumn - 1 }; + return model.getWordAtPosition(prevPos); + } + + const preWord = wordBefore(word); + const prePreWord = preWord && wordBefore(preWord); + + const oldStyle = prePreWord?.word === 'extern' && preWord?.word === 'crate'; + const newStyle = autocompleteOnUse && preWord?.word === 'use'; + + const triggerPrefix = oldStyle || newStyle; + + if (!triggerPrefix) { + return { suggestions: [] }; + } + + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn, + }; + + const suggestions = crates.map(({ name, version, id }) => ({ + kind: monaco.languages.CompletionItemKind.Module, + label: `${name} (${version})`, + insertText: `${id}; // ${version}`, + range, + })); + + return { suggestions }; + }, + }); + + return () => { + completionProvider.current?.dispose(); + }; + }, []), + ); - return () => { - completionProvider.current?.dispose(); - }; - }); + useEditorProp( + editor, + props.position, + useCallback((editor, _model, { line, column }) => { + editor.setPosition({ lineNumber: line, column }); + editor.focus(); + }, []), + ); return
; }; diff --git a/ui/frontend/editor/SimpleEditor.tsx b/ui/frontend/editor/SimpleEditor.tsx index 19e8a15d..3e82cbe7 100644 --- a/ui/frontend/editor/SimpleEditor.tsx +++ b/ui/frontend/editor/SimpleEditor.tsx @@ -1,4 +1,3 @@ -import { isEqual } from 'lodash-es'; import React from 'react'; import { CommonEditorProps, Position, Selection } from '../types'; @@ -16,11 +15,9 @@ class CodeByteOffsets { public lineToOffsets(line: number) { const precedingBytes = this.bytesBeforeLine(line); + const nextPrecedingBytes = this.bytesBeforeLine(line + 1); - const highlightedLine = this.lines[line]; - const highlightedBytes = highlightedLine.length; - - return [precedingBytes, precedingBytes + highlightedBytes]; + return [precedingBytes, nextPrecedingBytes]; } public rangeToOffsets(start: Position, end: Position) { @@ -41,7 +38,7 @@ class CodeByteOffsets { const precedingLines = this.lines.slice(0, line); // Add one to account for the newline we split on and removed - return precedingLines.map((l) => l.length + 1).reduce((a, b) => a + b); + return precedingLines.map((l) => l.length + 1).reduce((a, b) => a + b, 0); } } @@ -87,7 +84,7 @@ class SimpleEditor extends React.PureComponent { if (!newPosition || !editor) { return; } - if (isEqual(newPosition, oldPosition)) { + if (newPosition === oldPosition) { return; } @@ -104,7 +101,7 @@ class SimpleEditor extends React.PureComponent { if (!newSelection || !newSelection.start || !newSelection.end || !editor) { return; } - if (isEqual(newSelection, oldSelection)) { + if (newSelection === oldSelection) { return; }