Skip to content

Commit

Permalink
Merge pull request #1136 from rust-lang/editor-goto-line
Browse files Browse the repository at this point in the history
Enhance go-to-position for the simple and Monaco editors
  • Loading branch information
shepmaster authored Feb 10, 2025
2 parents b5353e6 + 8d359da commit 87a7451
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 85 deletions.
184 changes: 107 additions & 77 deletions ui/frontend/editor/MonacoEditorCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const MonacoEditorCore: React.FC<CommonEditorProps> = (props) => {
fontFamily: nodeStyle.fontFamily,
automaticLayout: true,
'semanticHighlighting.enabled': true,
autoClosingOvertype: 'always',
});
setEditor(editor);

Expand All @@ -81,93 +82,122 @@ const MonacoEditorCore: React.FC<CommonEditorProps> = (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 <div className={styles.monaco} ref={child} />;
};
Expand Down
13 changes: 5 additions & 8 deletions ui/frontend/editor/SimpleEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isEqual } from 'lodash-es';
import React from 'react';

import { CommonEditorProps, Position, Selection } from '../types';
Expand All @@ -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) {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -87,7 +84,7 @@ class SimpleEditor extends React.PureComponent<CommonEditorProps> {
if (!newPosition || !editor) {
return;
}
if (isEqual(newPosition, oldPosition)) {
if (newPosition === oldPosition) {
return;
}

Expand All @@ -104,7 +101,7 @@ class SimpleEditor extends React.PureComponent<CommonEditorProps> {
if (!newSelection || !newSelection.start || !newSelection.end || !editor) {
return;
}
if (isEqual(newSelection, oldSelection)) {
if (newSelection === oldSelection) {
return;
}

Expand Down

0 comments on commit 87a7451

Please sign in to comment.