Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/gotoSymbol/browser/symbolNavigation.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 { Emitter, Event } from '../../../../base/common/event.js';
7
import { KeyCode } from '../../../../base/common/keyCodes.js';
8
import { combinedDisposable, DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js';
9
import { isEqual } from '../../../../base/common/resources.js';
10
import { ICodeEditor } from '../../../browser/editorBrowser.js';
11
import { EditorCommand, registerEditorCommand } from '../../../browser/editorExtensions.js';
12
import { ICodeEditorService } from '../../../browser/services/codeEditorService.js';
13
import { Range } from '../../../common/core/range.js';
14
import { OneReference, ReferencesModel } from './referencesModel.js';
15
import { localize } from '../../../../nls.js';
16
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
17
import { TextEditorSelectionRevealType } from '../../../../platform/editor/common/editor.js';
18
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
19
import { createDecorator, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
20
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
21
import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
22
import { INotificationService, IStatusHandle } from '../../../../platform/notification/common/notification.js';
23
24
export const ctxHasSymbols = new RawContextKey('hasSymbols', false, localize('hasSymbols', "Whether there are symbol locations that can be navigated via keyboard-only."));
25
26
export const ISymbolNavigationService = createDecorator<ISymbolNavigationService>('ISymbolNavigationService');
27
28
export interface ISymbolNavigationService {
29
readonly _serviceBrand: undefined;
30
reset(): void;
31
put(anchor: OneReference): void;
32
revealNext(source: ICodeEditor): Promise<any>;
33
}
34
35
class SymbolNavigationService implements ISymbolNavigationService {
36
37
declare readonly _serviceBrand: undefined;
38
39
private readonly _ctxHasSymbols: IContextKey<boolean>;
40
41
private _currentModel?: ReferencesModel = undefined;
42
private _currentIdx: number = -1;
43
private _currentState?: IDisposable;
44
private _currentMessage?: IStatusHandle;
45
private _ignoreEditorChange: boolean = false;
46
47
constructor(
48
@IContextKeyService contextKeyService: IContextKeyService,
49
@ICodeEditorService private readonly _editorService: ICodeEditorService,
50
@INotificationService private readonly _notificationService: INotificationService,
51
@IKeybindingService private readonly _keybindingService: IKeybindingService,
52
) {
53
this._ctxHasSymbols = ctxHasSymbols.bindTo(contextKeyService);
54
}
55
56
reset(): void {
57
this._ctxHasSymbols.reset();
58
this._currentState?.dispose();
59
this._currentMessage?.close();
60
this._currentModel = undefined;
61
this._currentIdx = -1;
62
}
63
64
put(anchor: OneReference): void {
65
const refModel = anchor.parent.parent;
66
67
if (refModel.references.length <= 1) {
68
this.reset();
69
return;
70
}
71
72
this._currentModel = refModel;
73
this._currentIdx = refModel.references.indexOf(anchor);
74
this._ctxHasSymbols.set(true);
75
this._showMessage();
76
77
const editorState = new EditorState(this._editorService);
78
const listener = editorState.onDidChange(_ => {
79
80
if (this._ignoreEditorChange) {
81
return;
82
}
83
84
const editor = this._editorService.getActiveCodeEditor();
85
if (!editor) {
86
return;
87
}
88
const model = editor.getModel();
89
const position = editor.getPosition();
90
if (!model || !position) {
91
return;
92
}
93
94
let seenUri: boolean = false;
95
let seenPosition: boolean = false;
96
for (const reference of refModel.references) {
97
if (isEqual(reference.uri, model.uri)) {
98
seenUri = true;
99
seenPosition = seenPosition || Range.containsPosition(reference.range, position);
100
} else if (seenUri) {
101
break;
102
}
103
}
104
if (!seenUri || !seenPosition) {
105
this.reset();
106
}
107
});
108
109
this._currentState = combinedDisposable(editorState, listener);
110
}
111
112
revealNext(source: ICodeEditor): Promise<any> {
113
if (!this._currentModel) {
114
return Promise.resolve();
115
}
116
117
// get next result and advance
118
this._currentIdx += 1;
119
this._currentIdx %= this._currentModel.references.length;
120
const reference = this._currentModel.references[this._currentIdx];
121
122
// status
123
this._showMessage();
124
125
// open editor, ignore events while that happens
126
this._ignoreEditorChange = true;
127
return this._editorService.openCodeEditor({
128
resource: reference.uri,
129
options: {
130
selection: Range.collapseToStart(reference.range),
131
selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport
132
}
133
}, source).finally(() => {
134
this._ignoreEditorChange = false;
135
});
136
137
}
138
139
private _showMessage(): void {
140
141
this._currentMessage?.close();
142
143
const kb = this._keybindingService.lookupKeybinding('editor.gotoNextSymbolFromResult');
144
const message = kb
145
? localize('location.kb', "Symbol {0} of {1}, {2} for next", this._currentIdx + 1, this._currentModel!.references.length, kb.getLabel())
146
: localize('location', "Symbol {0} of {1}", this._currentIdx + 1, this._currentModel!.references.length);
147
148
this._currentMessage = this._notificationService.status(message);
149
}
150
}
151
152
registerSingleton(ISymbolNavigationService, SymbolNavigationService, InstantiationType.Delayed);
153
154
registerEditorCommand(new class extends EditorCommand {
155
156
constructor() {
157
super({
158
id: 'editor.gotoNextSymbolFromResult',
159
precondition: ctxHasSymbols,
160
kbOpts: {
161
weight: KeybindingWeight.EditorContrib,
162
primary: KeyCode.F12
163
}
164
});
165
}
166
167
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
168
return accessor.get(ISymbolNavigationService).revealNext(editor);
169
}
170
});
171
172
KeybindingsRegistry.registerCommandAndKeybindingRule({
173
id: 'editor.gotoNextSymbolFromResult.cancel',
174
weight: KeybindingWeight.EditorContrib,
175
when: ctxHasSymbols,
176
primary: KeyCode.Escape,
177
handler(accessor) {
178
accessor.get(ISymbolNavigationService).reset();
179
}
180
});
181
182
//
183
184
class EditorState {
185
186
private readonly _listener = new Map<ICodeEditor, IDisposable>();
187
private readonly _disposables = new DisposableStore();
188
189
private readonly _onDidChange = new Emitter<{ editor: ICodeEditor }>();
190
readonly onDidChange: Event<{ editor: ICodeEditor }> = this._onDidChange.event;
191
192
constructor(@ICodeEditorService editorService: ICodeEditorService) {
193
this._disposables.add(editorService.onCodeEditorRemove(this._onDidRemoveEditor, this));
194
this._disposables.add(editorService.onCodeEditorAdd(this._onDidAddEditor, this));
195
editorService.listCodeEditors().forEach(this._onDidAddEditor, this);
196
}
197
198
dispose(): void {
199
this._disposables.dispose();
200
this._onDidChange.dispose();
201
dispose(this._listener.values());
202
}
203
204
private _onDidAddEditor(editor: ICodeEditor): void {
205
this._listener.set(editor, combinedDisposable(
206
editor.onDidChangeCursorPosition(_ => this._onDidChange.fire({ editor })),
207
editor.onDidChangeModelContent(_ => this._onDidChange.fire({ editor })),
208
));
209
}
210
211
private _onDidRemoveEditor(editor: ICodeEditor): void {
212
this._listener.get(editor)?.dispose();
213
this._listener.delete(editor);
214
}
215
}
216
217