Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
8
import { getCodeEditor } from '../../../browser/editorBrowser.js';
9
import { EditorOption, RenderLineNumbersType } from '../../../common/config/editorOptions.js';
10
import { IPosition } from '../../../common/core/position.js';
11
import { IRange } from '../../../common/core/range.js';
12
import { IEditor, ScrollType } from '../../../common/editorCommon.js';
13
import { AbstractEditorNavigationQuickAccessProvider, IQuickAccessTextEditorContext } from './editorNavigationQuickAccess.js';
14
import { localize } from '../../../../nls.js';
15
import { IQuickPick, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
16
17
interface IGotoLineQuickPickItem extends IQuickPickItem, Partial<IPosition> { }
18
19
export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
20
21
static PREFIX = ':';
22
23
constructor() {
24
super({ canAcceptInBackground: true });
25
}
26
27
protected provideWithoutTextEditor(picker: IQuickPick<IGotoLineQuickPickItem, { useSeparators: true }>): IDisposable {
28
const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line.");
29
30
picker.items = [{ label }];
31
picker.ariaLabel = label;
32
33
return Disposable.None;
34
}
35
36
protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IGotoLineQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {
37
const editor = context.editor;
38
const disposables = new DisposableStore();
39
40
// Goto line once picked
41
disposables.add(picker.onDidAccept(event => {
42
const [item] = picker.selectedItems;
43
if (item) {
44
if (!this.isValidLineNumber(editor, item.lineNumber)) {
45
return;
46
}
47
48
this.gotoLocation(context, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground });
49
50
if (!event.inBackground) {
51
picker.hide();
52
}
53
}
54
}));
55
56
// React to picker changes
57
const updatePickerAndEditor = () => {
58
const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length));
59
const label = this.getPickLabel(editor, position.lineNumber, position.column);
60
61
// Picker
62
picker.items = [{
63
lineNumber: position.lineNumber,
64
column: position.column,
65
label
66
}];
67
68
// ARIA Label
69
picker.ariaLabel = label;
70
71
// Clear decorations for invalid range
72
if (!this.isValidLineNumber(editor, position.lineNumber)) {
73
this.clearDecorations(editor);
74
return;
75
}
76
77
// Reveal
78
const range = this.toRange(position.lineNumber, position.column);
79
editor.revealRangeInCenter(range, ScrollType.Smooth);
80
81
// Decorate
82
this.addDecorations(editor, range);
83
};
84
updatePickerAndEditor();
85
disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor()));
86
87
// Adjust line number visibility as needed
88
const codeEditor = getCodeEditor(editor);
89
if (codeEditor) {
90
const options = codeEditor.getOptions();
91
const lineNumbers = options.get(EditorOption.lineNumbers);
92
if (lineNumbers.renderType === RenderLineNumbersType.Relative) {
93
codeEditor.updateOptions({ lineNumbers: 'on' });
94
95
disposables.add(toDisposable(() => codeEditor.updateOptions({ lineNumbers: 'relative' })));
96
}
97
}
98
99
return disposables;
100
}
101
102
private toRange(lineNumber = 1, column = 1): IRange {
103
return {
104
startLineNumber: lineNumber,
105
startColumn: column,
106
endLineNumber: lineNumber,
107
endColumn: column
108
};
109
}
110
111
private parsePosition(editor: IEditor, value: string): IPosition {
112
113
// Support line-col formats of `line,col`, `line:col`, `line#col`
114
const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part));
115
const endLine = this.lineCount(editor) + 1;
116
117
return {
118
lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0],
119
column: numbers[1]
120
};
121
}
122
123
private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined): string {
124
125
// Location valid: indicate this as picker label
126
if (this.isValidLineNumber(editor, lineNumber)) {
127
if (this.isValidColumn(editor, lineNumber, column)) {
128
return localize('gotoLineColumnLabel', "Go to line {0} and character {1}.", lineNumber, column);
129
}
130
131
return localize('gotoLineLabel', "Go to line {0}.", lineNumber);
132
}
133
134
// Location invalid: show generic label
135
const position = editor.getPosition() || { lineNumber: 1, column: 1 };
136
const lineCount = this.lineCount(editor);
137
if (lineCount > 1) {
138
return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount);
139
}
140
141
return localize('gotoLineLabelEmpty', "Current Line: {0}, Character: {1}. Type a line number to navigate to.", position.lineNumber, position.column);
142
}
143
144
private isValidLineNumber(editor: IEditor, lineNumber: number | undefined): boolean {
145
if (!lineNumber || typeof lineNumber !== 'number') {
146
return false;
147
}
148
149
return lineNumber > 0 && lineNumber <= this.lineCount(editor);
150
}
151
152
private isValidColumn(editor: IEditor, lineNumber: number, column: number | undefined): boolean {
153
if (!column || typeof column !== 'number') {
154
return false;
155
}
156
157
const model = this.getModel(editor);
158
if (!model) {
159
return false;
160
}
161
162
const positionCandidate = { lineNumber, column };
163
164
return model.validatePosition(positionCandidate).equals(positionCandidate);
165
}
166
167
private lineCount(editor: IEditor): number {
168
return this.getModel(editor)?.getLineCount() ?? 0;
169
}
170
}
171
172