monaco-editor-core
Version:
A browser based code editor
127 lines (126 loc) • 5.89 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { getCodeEditor } from '../../../browser/editorBrowser.js';
import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js';
import { localize } from '../../../../nls.js';
export class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
static { this.PREFIX = ':'; }
constructor() {
super({ canAcceptInBackground: true });
}
provideWithoutTextEditor(picker) {
const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line.");
picker.items = [{ label }];
picker.ariaLabel = label;
return Disposable.None;
}
provideWithTextEditor(context, picker, token) {
const editor = context.editor;
const disposables = new DisposableStore();
// Goto line once picked
disposables.add(picker.onDidAccept(event => {
const [item] = picker.selectedItems;
if (item) {
if (!this.isValidLineNumber(editor, item.lineNumber)) {
return;
}
this.gotoLocation(context, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground });
if (!event.inBackground) {
picker.hide();
}
}
}));
// React to picker changes
const updatePickerAndEditor = () => {
const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length));
const label = this.getPickLabel(editor, position.lineNumber, position.column);
// Picker
picker.items = [{
lineNumber: position.lineNumber,
column: position.column,
label
}];
// ARIA Label
picker.ariaLabel = label;
// Clear decorations for invalid range
if (!this.isValidLineNumber(editor, position.lineNumber)) {
this.clearDecorations(editor);
return;
}
// Reveal
const range = this.toRange(position.lineNumber, position.column);
editor.revealRangeInCenter(range, 0 /* ScrollType.Smooth */);
// Decorate
this.addDecorations(editor, range);
};
updatePickerAndEditor();
disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor()));
// Adjust line number visibility as needed
const codeEditor = getCodeEditor(editor);
if (codeEditor) {
const options = codeEditor.getOptions();
const lineNumbers = options.get(68 /* EditorOption.lineNumbers */);
if (lineNumbers.renderType === 2 /* RenderLineNumbersType.Relative */) {
codeEditor.updateOptions({ lineNumbers: 'on' });
disposables.add(toDisposable(() => codeEditor.updateOptions({ lineNumbers: 'relative' })));
}
}
return disposables;
}
toRange(lineNumber = 1, column = 1) {
return {
startLineNumber: lineNumber,
startColumn: column,
endLineNumber: lineNumber,
endColumn: column
};
}
parsePosition(editor, value) {
// Support line-col formats of `line,col`, `line:col`, `line#col`
const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part));
const endLine = this.lineCount(editor) + 1;
return {
lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0],
column: numbers[1]
};
}
getPickLabel(editor, lineNumber, column) {
// Location valid: indicate this as picker label
if (this.isValidLineNumber(editor, lineNumber)) {
if (this.isValidColumn(editor, lineNumber, column)) {
return localize('gotoLineColumnLabel', "Go to line {0} and character {1}.", lineNumber, column);
}
return localize('gotoLineLabel', "Go to line {0}.", lineNumber);
}
// Location invalid: show generic label
const position = editor.getPosition() || { lineNumber: 1, column: 1 };
const lineCount = this.lineCount(editor);
if (lineCount > 1) {
return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount);
}
return localize('gotoLineLabelEmpty', "Current Line: {0}, Character: {1}. Type a line number to navigate to.", position.lineNumber, position.column);
}
isValidLineNumber(editor, lineNumber) {
if (!lineNumber || typeof lineNumber !== 'number') {
return false;
}
return lineNumber > 0 && lineNumber <= this.lineCount(editor);
}
isValidColumn(editor, lineNumber, column) {
if (!column || typeof column !== 'number') {
return false;
}
const model = this.getModel(editor);
if (!model) {
return false;
}
const positionCandidate = { lineNumber, column };
return model.validatePosition(positionCandidate).equals(positionCandidate);
}
lineCount(editor) {
return this.getModel(editor)?.getLineCount() ?? 0;
}
}