Path: blob/main/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../base/common/cancellation.js';6import { Event } from '../../../../base/common/event.js';7import { createSingleCallFunction } from '../../../../base/common/functional.js';8import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';9import { getCodeEditor, isDiffEditor } from '../../../browser/editorBrowser.js';10import { IRange } from '../../../common/core/range.js';11import { IDiffEditor, IEditor, ScrollType } from '../../../common/editorCommon.js';12import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../common/model.js';13import { overviewRulerRangeHighlight } from '../../../common/core/editorColorRegistry.js';14import { IQuickAccessProvider, IQuickAccessProviderRunOptions } from '../../../../platform/quickinput/common/quickAccess.js';15import { IKeyMods, IQuickPick, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';16import { themeColorFromId } from '../../../../platform/theme/common/themeService.js';17import { status } from '../../../../base/browser/ui/aria/aria.js';18import { TextEditorSelectionSource } from '../../../../platform/editor/common/editor.js';1920interface IEditorLineDecoration {21readonly rangeHighlightId: string;22readonly overviewRulerDecorationId: string;23}2425export interface IEditorNavigationQuickAccessOptions {26canAcceptInBackground?: boolean;27}2829export interface IQuickAccessTextEditorContext {3031/**32* The current active editor.33*/34readonly editor: IEditor;3536/**37* If defined, allows to restore the original view state38* the text editor had before quick access opened.39*/40restoreViewState?: () => void;41}4243/**44* A reusable quick access provider for the editor with support45* for adding decorations for navigating in the currently active file46* (for example "Go to line", "Go to symbol").47*/48export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider {4950constructor(protected options?: IEditorNavigationQuickAccessOptions) { }5152//#region Provider methods5354provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {55const disposables = new DisposableStore();5657// Apply options if any58picker.canAcceptInBackground = !!this.options?.canAcceptInBackground;5960// Disable filtering & sorting, we control the results61picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;6263// Provide based on current active editor64const pickerDisposable = disposables.add(new MutableDisposable());65pickerDisposable.value = this.doProvide(picker, token, runOptions);6667// Re-create whenever the active editor changes68disposables.add(this.onDidActiveTextEditorControlChange(() => {6970// Clear old71pickerDisposable.value = undefined;7273// Add new74pickerDisposable.value = this.doProvide(picker, token);75}));7677return disposables;78}7980private doProvide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {81const disposables = new DisposableStore();8283// With text control84const editor = this.activeTextEditorControl;85if (editor && this.canProvideWithTextEditor(editor)) {86const context: IQuickAccessTextEditorContext = { editor };8788// Restore any view state if this picker was closed89// without actually going to a line90const codeEditor = getCodeEditor(editor);91if (codeEditor) {9293// Remember view state and update it when the cursor position94// changes even later because it could be that the user has95// configured quick access to remain open when focus is lost and96// we always want to restore the current location.97let lastKnownEditorViewState = editor.saveViewState() ?? undefined;98disposables.add(codeEditor.onDidChangeCursorPosition(() => {99lastKnownEditorViewState = editor.saveViewState() ?? undefined;100}));101102context.restoreViewState = () => {103if (lastKnownEditorViewState && editor === this.activeTextEditorControl) {104editor.restoreViewState(lastKnownEditorViewState);105}106};107108disposables.add(createSingleCallFunction(token.onCancellationRequested)(() => context.restoreViewState?.()));109}110111// Clean up decorations on dispose112disposables.add(toDisposable(() => this.clearDecorations(editor)));113114// Ask subclass for entries115disposables.add(this.provideWithTextEditor(context, picker, token, runOptions));116}117118// Without text control119else {120disposables.add(this.provideWithoutTextEditor(picker, token));121}122123return disposables;124}125126/**127* Subclasses to implement if they can operate on the text editor.128*/129protected canProvideWithTextEditor(editor: IEditor): boolean {130return true;131}132133/**134* Subclasses to implement to provide picks for the picker when an editor is active.135*/136protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable;137138/**139* Subclasses to implement to provide picks for the picker when no editor is active.140*/141protected abstract provideWithoutTextEditor(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable;142143protected gotoLocation({ editor }: IQuickAccessTextEditorContext, options: { range: IRange; keyMods: IKeyMods; forceSideBySide?: boolean; preserveFocus?: boolean }): void {144editor.setSelection(options.range, TextEditorSelectionSource.JUMP);145editor.revealRangeInCenter(options.range, ScrollType.Smooth);146if (!options.preserveFocus) {147editor.focus();148}149const model = editor.getModel();150if (model && 'getLineContent' in model) {151status(`${model.getLineContent(options.range.startLineNumber)}`);152}153}154155protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {156return isDiffEditor(editor) ?157editor.getModel()?.modified :158editor.getModel() as ITextModel;159}160161//#endregion162163164//#region Editor access165166/**167* Subclasses to provide an event when the active editor control changes.168*/169protected abstract readonly onDidActiveTextEditorControlChange: Event<void>;170171/**172* Subclasses to provide the current active editor control.173*/174protected abstract activeTextEditorControl: IEditor | undefined;175176//#endregion177178179//#region Decorations Utils180181private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined;182183addDecorations(editor: IEditor, range: IRange): void {184editor.changeDecorations(changeAccessor => {185186// Reset old decorations if any187const deleteDecorations: string[] = [];188if (this.rangeHighlightDecorationId) {189deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId);190deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);191192this.rangeHighlightDecorationId = undefined;193}194195// Add new decorations for the range196const newDecorations: IModelDeltaDecoration[] = [197198// highlight the entire line on the range199{200range,201options: {202description: 'quick-access-range-highlight',203className: 'rangeHighlight',204isWholeLine: true205}206},207208// also add overview ruler highlight209{210range,211options: {212description: 'quick-access-range-highlight-overview',213overviewRuler: {214color: themeColorFromId(overviewRulerRangeHighlight),215position: OverviewRulerLane.Full216}217}218}219];220221const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);222223this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId };224});225}226227clearDecorations(editor: IEditor): void {228const rangeHighlightDecorationId = this.rangeHighlightDecorationId;229if (rangeHighlightDecorationId) {230editor.changeDecorations(changeAccessor => {231changeAccessor.deltaDecorations([232rangeHighlightDecorationId.overviewRulerDecorationId,233rangeHighlightDecorationId.rangeHighlightId234], []);235});236237this.rangeHighlightDecorationId = undefined;238}239}240241//#endregion242}243244245