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