Path: blob/main/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts
5240 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 { localize } from '../../../../nls.js';10import { getCodeEditor, isDiffEditor } from '../../../browser/editorBrowser.js';11import { IRange } from '../../../common/core/range.js';12import { IDiffEditor, IEditor, ScrollType } from '../../../common/editorCommon.js';13import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../common/model.js';14import { overviewRulerRangeHighlight } from '../../../common/core/editorColorRegistry.js';15import { IQuickAccessProvider, IQuickAccessProviderRunOptions } from '../../../../platform/quickinput/common/quickAccess.js';16import { IKeyMods, IQuickPick, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';17import { themeColorFromId } from '../../../../platform/theme/common/themeService.js';18import { status } from '../../../../base/browser/ui/aria/aria.js';19import { TextEditorSelectionSource } from '../../../../platform/editor/common/editor.js';2021interface IEditorLineDecoration {22readonly rangeHighlightId: string;23readonly overviewRulerDecorationId: string;24}2526export interface IEditorNavigationQuickAccessOptions {27canAcceptInBackground?: boolean;28}2930export interface IQuickAccessTextEditorContext {3132/**33* The current active editor.34*/35readonly editor: IEditor;3637/**38* If defined, allows to restore the original view state39* the text editor had before quick access opened.40*/41restoreViewState?: () => void;42}4344/**45* A reusable quick access provider for the editor with support46* for adding decorations for navigating in the currently active file47* (for example "Go to line", "Go to symbol").48*/49export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider {5051constructor(protected options?: IEditorNavigationQuickAccessOptions) { }5253//#region Provider methods5455provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {56const disposables = new DisposableStore();5758// Apply options if any59picker.canAcceptInBackground = !!this.options?.canAcceptInBackground;6061// Disable filtering & sorting, we control the results62picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;6364// Provide based on current active editor65const pickerDisposable = disposables.add(new MutableDisposable());66pickerDisposable.value = this.doProvide(picker, token, runOptions);6768// Re-create whenever the active editor changes69disposables.add(this.onDidActiveTextEditorControlChange(() => {7071// Clear old72pickerDisposable.value = undefined;7374// Add new75pickerDisposable.value = this.doProvide(picker, token);76}));7778return disposables;79}8081private doProvide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {82const disposables = new DisposableStore();8384// With text control85const editor = this.activeTextEditorControl;86if (editor && this.canProvideWithTextEditor(editor)) {87const context: IQuickAccessTextEditorContext = { editor };8889// Restore any view state if this picker was closed90// without actually going to a line91const codeEditor = getCodeEditor(editor);92if (codeEditor) {9394// Remember view state and update it when the cursor position95// changes even later because it could be that the user has96// configured quick access to remain open when focus is lost and97// we always want to restore the current location.98let lastKnownEditorViewState = editor.saveViewState() ?? undefined;99disposables.add(codeEditor.onDidChangeCursorPosition(() => {100lastKnownEditorViewState = editor.saveViewState() ?? undefined;101}));102103context.restoreViewState = () => {104if (lastKnownEditorViewState && editor === this.activeTextEditorControl) {105editor.restoreViewState(lastKnownEditorViewState);106}107};108109disposables.add(createSingleCallFunction(token.onCancellationRequested)(() => context.restoreViewState?.()));110}111112// Clean up decorations on dispose113disposables.add(toDisposable(() => this.clearDecorations(editor)));114115// Ask subclass for entries116disposables.add(this.provideWithTextEditor(context, picker, token, runOptions));117}118119// Without text control120else {121disposables.add(this.provideWithoutTextEditor(picker, token));122}123124return disposables;125}126127/**128* Subclasses to implement if they can operate on the text editor.129*/130protected canProvideWithTextEditor(editor: IEditor): boolean {131return true;132}133134/**135* Subclasses to implement to provide picks for the picker when an editor is active.136*/137protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable;138139/**140* Subclasses to implement to provide picks for the picker when no editor is active.141*/142protected abstract provideWithoutTextEditor(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable;143144protected gotoLocation({ editor }: IQuickAccessTextEditorContext, options: { range: IRange; keyMods: IKeyMods; forceSideBySide?: boolean; preserveFocus?: boolean }): void {145editor.setSelection(options.range, TextEditorSelectionSource.JUMP);146editor.revealRangeInCenter(options.range, ScrollType.Smooth);147if (!options.preserveFocus) {148editor.focus();149}150151const model = this.getModel(editor);152if (model) {153const lineContent = model.getLineContent(options.range.startLineNumber);154status(localize('gotoLocation.status', "Line {0}, column {1}: {2}", options.range.startLineNumber, options.range.startColumn, lineContent));155}156}157158protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {159return isDiffEditor(editor) ?160editor.getModel()?.modified :161editor.getModel() as ITextModel;162}163164//#endregion165166167//#region Editor access168169/**170* Subclasses to provide an event when the active editor control changes.171*/172protected abstract readonly onDidActiveTextEditorControlChange: Event<void>;173174/**175* Subclasses to provide the current active editor control.176*/177protected abstract activeTextEditorControl: IEditor | undefined;178179//#endregion180181182//#region Decorations Utils183184private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined;185186addDecorations(editor: IEditor, range: IRange): void {187editor.changeDecorations(changeAccessor => {188189// Reset old decorations if any190const deleteDecorations: string[] = [];191if (this.rangeHighlightDecorationId) {192deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId);193deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);194195this.rangeHighlightDecorationId = undefined;196}197198// Add new decorations for the range199const newDecorations: IModelDeltaDecoration[] = [200201// highlight the entire line on the range202{203range,204options: {205description: 'quick-access-range-highlight',206className: 'rangeHighlight',207isWholeLine: true208}209},210211// also add overview ruler highlight212{213range,214options: {215description: 'quick-access-range-highlight-overview',216overviewRuler: {217color: themeColorFromId(overviewRulerRangeHighlight),218position: OverviewRulerLane.Full219}220}221}222];223224const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);225226this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId };227});228}229230clearDecorations(editor: IEditor): void {231const rangeHighlightDecorationId = this.rangeHighlightDecorationId;232if (rangeHighlightDecorationId) {233editor.changeDecorations(changeAccessor => {234changeAccessor.deltaDecorations([235rangeHighlightDecorationId.overviewRulerDecorationId,236rangeHighlightDecorationId.rangeHighlightId237], []);238});239240this.rangeHighlightDecorationId = undefined;241}242}243244//#endregion245}246247248