Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts
5240 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 { Event } from '../../../../base/common/event.js';
8
import { createSingleCallFunction } from '../../../../base/common/functional.js';
9
import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
10
import { localize } from '../../../../nls.js';
11
import { getCodeEditor, isDiffEditor } from '../../../browser/editorBrowser.js';
12
import { IRange } from '../../../common/core/range.js';
13
import { IDiffEditor, IEditor, ScrollType } from '../../../common/editorCommon.js';
14
import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../common/model.js';
15
import { overviewRulerRangeHighlight } from '../../../common/core/editorColorRegistry.js';
16
import { IQuickAccessProvider, IQuickAccessProviderRunOptions } from '../../../../platform/quickinput/common/quickAccess.js';
17
import { IKeyMods, IQuickPick, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
18
import { themeColorFromId } from '../../../../platform/theme/common/themeService.js';
19
import { status } from '../../../../base/browser/ui/aria/aria.js';
20
import { TextEditorSelectionSource } from '../../../../platform/editor/common/editor.js';
21
22
interface IEditorLineDecoration {
23
readonly rangeHighlightId: string;
24
readonly overviewRulerDecorationId: string;
25
}
26
27
export interface IEditorNavigationQuickAccessOptions {
28
canAcceptInBackground?: boolean;
29
}
30
31
export interface IQuickAccessTextEditorContext {
32
33
/**
34
* The current active editor.
35
*/
36
readonly editor: IEditor;
37
38
/**
39
* If defined, allows to restore the original view state
40
* the text editor had before quick access opened.
41
*/
42
restoreViewState?: () => void;
43
}
44
45
/**
46
* A reusable quick access provider for the editor with support
47
* for adding decorations for navigating in the currently active file
48
* (for example "Go to line", "Go to symbol").
49
*/
50
export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider {
51
52
constructor(protected options?: IEditorNavigationQuickAccessOptions) { }
53
54
//#region Provider methods
55
56
provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
57
const disposables = new DisposableStore();
58
59
// Apply options if any
60
picker.canAcceptInBackground = !!this.options?.canAcceptInBackground;
61
62
// Disable filtering & sorting, we control the results
63
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
64
65
// Provide based on current active editor
66
const pickerDisposable = disposables.add(new MutableDisposable());
67
pickerDisposable.value = this.doProvide(picker, token, runOptions);
68
69
// Re-create whenever the active editor changes
70
disposables.add(this.onDidActiveTextEditorControlChange(() => {
71
72
// Clear old
73
pickerDisposable.value = undefined;
74
75
// Add new
76
pickerDisposable.value = this.doProvide(picker, token);
77
}));
78
79
return disposables;
80
}
81
82
private doProvide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
83
const disposables = new DisposableStore();
84
85
// With text control
86
const editor = this.activeTextEditorControl;
87
if (editor && this.canProvideWithTextEditor(editor)) {
88
const context: IQuickAccessTextEditorContext = { editor };
89
90
// Restore any view state if this picker was closed
91
// without actually going to a line
92
const codeEditor = getCodeEditor(editor);
93
if (codeEditor) {
94
95
// Remember view state and update it when the cursor position
96
// changes even later because it could be that the user has
97
// configured quick access to remain open when focus is lost and
98
// we always want to restore the current location.
99
let lastKnownEditorViewState = editor.saveViewState() ?? undefined;
100
disposables.add(codeEditor.onDidChangeCursorPosition(() => {
101
lastKnownEditorViewState = editor.saveViewState() ?? undefined;
102
}));
103
104
context.restoreViewState = () => {
105
if (lastKnownEditorViewState && editor === this.activeTextEditorControl) {
106
editor.restoreViewState(lastKnownEditorViewState);
107
}
108
};
109
110
disposables.add(createSingleCallFunction(token.onCancellationRequested)(() => context.restoreViewState?.()));
111
}
112
113
// Clean up decorations on dispose
114
disposables.add(toDisposable(() => this.clearDecorations(editor)));
115
116
// Ask subclass for entries
117
disposables.add(this.provideWithTextEditor(context, picker, token, runOptions));
118
}
119
120
// Without text control
121
else {
122
disposables.add(this.provideWithoutTextEditor(picker, token));
123
}
124
125
return disposables;
126
}
127
128
/**
129
* Subclasses to implement if they can operate on the text editor.
130
*/
131
protected canProvideWithTextEditor(editor: IEditor): boolean {
132
return true;
133
}
134
135
/**
136
* Subclasses to implement to provide picks for the picker when an editor is active.
137
*/
138
protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable;
139
140
/**
141
* Subclasses to implement to provide picks for the picker when no editor is active.
142
*/
143
protected abstract provideWithoutTextEditor(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable;
144
145
protected gotoLocation({ editor }: IQuickAccessTextEditorContext, options: { range: IRange; keyMods: IKeyMods; forceSideBySide?: boolean; preserveFocus?: boolean }): void {
146
editor.setSelection(options.range, TextEditorSelectionSource.JUMP);
147
editor.revealRangeInCenter(options.range, ScrollType.Smooth);
148
if (!options.preserveFocus) {
149
editor.focus();
150
}
151
152
const model = this.getModel(editor);
153
if (model) {
154
const lineContent = model.getLineContent(options.range.startLineNumber);
155
status(localize('gotoLocation.status', "Line {0}, column {1}: {2}", options.range.startLineNumber, options.range.startColumn, lineContent));
156
}
157
}
158
159
protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {
160
return isDiffEditor(editor) ?
161
editor.getModel()?.modified :
162
editor.getModel() as ITextModel;
163
}
164
165
//#endregion
166
167
168
//#region Editor access
169
170
/**
171
* Subclasses to provide an event when the active editor control changes.
172
*/
173
protected abstract readonly onDidActiveTextEditorControlChange: Event<void>;
174
175
/**
176
* Subclasses to provide the current active editor control.
177
*/
178
protected abstract activeTextEditorControl: IEditor | undefined;
179
180
//#endregion
181
182
183
//#region Decorations Utils
184
185
private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined;
186
187
addDecorations(editor: IEditor, range: IRange): void {
188
editor.changeDecorations(changeAccessor => {
189
190
// Reset old decorations if any
191
const deleteDecorations: string[] = [];
192
if (this.rangeHighlightDecorationId) {
193
deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId);
194
deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);
195
196
this.rangeHighlightDecorationId = undefined;
197
}
198
199
// Add new decorations for the range
200
const newDecorations: IModelDeltaDecoration[] = [
201
202
// highlight the entire line on the range
203
{
204
range,
205
options: {
206
description: 'quick-access-range-highlight',
207
className: 'rangeHighlight',
208
isWholeLine: true
209
}
210
},
211
212
// also add overview ruler highlight
213
{
214
range,
215
options: {
216
description: 'quick-access-range-highlight-overview',
217
overviewRuler: {
218
color: themeColorFromId(overviewRulerRangeHighlight),
219
position: OverviewRulerLane.Full
220
}
221
}
222
}
223
];
224
225
const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);
226
227
this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId };
228
});
229
}
230
231
clearDecorations(editor: IEditor): void {
232
const rangeHighlightDecorationId = this.rangeHighlightDecorationId;
233
if (rangeHighlightDecorationId) {
234
editor.changeDecorations(changeAccessor => {
235
changeAccessor.deltaDecorations([
236
rangeHighlightDecorationId.overviewRulerDecorationId,
237
rangeHighlightDecorationId.rangeHighlightId
238
], []);
239
});
240
241
this.rangeHighlightDecorationId = undefined;
242
}
243
}
244
245
//#endregion
246
}
247
248