diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index afc3f54369b77..a73c3d7072c5c 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -8,6 +8,12 @@ import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/con import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { getIEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; export const inQuickPickContextKeyValue = 'inQuickOpen'; export const InQuickPickContextKey = new RawContextKey(inQuickPickContextKeyValue, false, localize('inQuickOpen', "Whether keyboard focus is inside the quick open control")); @@ -45,3 +51,42 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan quickInputService.navigate(!!next, quickNavigate); }; } +export class EditorViewState { + private _editorViewState: { + editor: EditorInput; + group: IEditorGroup; + state: ICodeEditorViewState | IDiffEditorViewState | undefined; + } | undefined = undefined; + + constructor(private readonly editorService: IEditorService) { } + + set(): void { + if (this._editorViewState) { + return; // return early if already done + } + + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + this._editorViewState = { + group: activeEditorPane.group, + editor: activeEditorPane.input, + state: getIEditor(activeEditorPane.getControl())?.saveViewState() ?? undefined, + }; + } + } + + async restore(): Promise { + if (this._editorViewState) { + const options: IEditorOptions = { + viewState: this._editorViewState.state, + preserveFocus: true /* import to not close the picker as a result */ + }; + + await this._editorViewState.group.openEditor(this._editorViewState.editor, options); + } + } + + reset() { + this._editorViewState = undefined; + } +} diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 00c019964888b..03a37d5384ed7 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -35,19 +35,17 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { top } from 'vs/base/common/arrays'; import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IEditorOptions, IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ResourceMap } from 'vs/base/common/map'; import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; import { AnythingQuickAccessProviderRunOptions, DefaultQuickAccessFilterValue, Extensions, IQuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; -import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; +import { EditorViewState, IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ScrollType, IEditor, ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; +import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { Event } from 'vs/base/common/event'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { getIEditor } from 'vs/editor/browser/editorBrowser'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -89,11 +87,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider | undefined = undefined; - editorViewState: { - editor: EditorInput; - group: IEditorGroup; - state: ICodeEditorViewState | IDiffEditorViewState | undefined; - } | undefined = undefined; + editorViewState: EditorViewState; scorerCache: FuzzyScorerCache = Object.create(null); fileQueryCache: FileQueryCacheState | undefined = undefined; @@ -106,7 +100,9 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider): void { @@ -131,33 +127,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { - if (this.editorViewState) { - const options: IEditorOptions = { - viewState: this.editorViewState.state, - preserveFocus: true /* import to not close the picker as a result */ - }; - - await this.editorViewState.group.openEditor(this.editorViewState.editor, options); - } + this.editorViewState.reset(); } }(this, this.editorService); @@ -237,7 +207,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { if (reason === QuickInputHideReason.Gesture) { - this.pickState.restoreEditorViewState(); + this.pickState.editorViewState.restore(); } })); @@ -259,7 +229,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { +interface ITextSearchQuickAccessItem extends IPickerQuickAccessItem { + match?: Match; +} +export class TextSearchQuickAccess extends PickerQuickAccessProvider { private queryBuilder: QueryBuilder; private searchModel: SearchModel; private currentAsyncSearch: Promise = Promise.resolve({ results: [], messages: [] }); + private storedOriginalLocation = false; + private readonly editorViewState = new EditorViewState( + this._editorService + ); private _getTextQueryBuilderOptions(charsPerLine: number): ITextQueryBuilderOptions { return { @@ -72,7 +81,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + override provide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); if (TEXT_SEARCH_QUICK_ACCESS_PREFIX.length < picker.value.length) { picker.valueSelection = [TEXT_SEARCH_QUICK_ACCESS_PREFIX.length, picker.value.length]; } picker.customButton = true; picker.customLabel = '$(link-external)'; - picker.onDidCustom(() => { + disposables.add(picker.onDidCustom(() => { if (this.searchModel.searchResult.count() > 0) { this.moveToSearchViewlet(undefined); } else { this._viewsService.openView(VIEW_ID, true); } picker.hide(); - }); + })); + disposables.add(picker.onDidChangeActive(() => { + const [item] = picker.activeItems; + + if (item?.match) { + // only store location once, or else it will store new state every time we change active pick + if (!this.storedOriginalLocation) { + // we must remember our curret view state to be able to restore + this.editorViewState.set(); + this.storedOriginalLocation = true; + } + // open it + this._editorService.openEditor({ + resource: item.match.parent().resource, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: item.match.range() } + }); + } + })); + + disposables.add(Event.once(picker.onDidHide)(({ reason }) => { + // Restore view state upon cancellation if we changed it + // but only when the picker was closed via explicit user + // gesture and not e.g. when focus was lost because that + // could mean the user clicked into the editor directly. + if (reason === QuickInputHideReason.Gesture) { + this.editorViewState.restore(); + } + this.searchModel.searchResult.toggleHighlights(false); + })); + disposables.add(super.provide(picker, token, runOptions)); - disposables.add(picker.onDidHide(() => this.searchModel.searchResult.toggleHighlights(false))); disposables.add(picker.onDidAccept(() => this.searchModel.searchResult.toggleHighlights(false))); return disposables; } @@ -177,11 +214,11 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider limit ? matches.slice(0, limit) : matches; - const picks: Array = []; + const picks: Array = []; for (let fileIndex = 0; fileIndex < matches.length; fileIndex++) { if (fileIndex === limit) { @@ -258,7 +295,8 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { this.moveToSearchViewlet(element); return TriggerAction.CLOSE_PICKER; - } + }, + match: element }); } }