Skip to content

Commit 404f9d1

Browse files
authored
Implement preview on highlighting quickpick in quick search (#202306)
1 parent dc545a6 commit 404f9d1

File tree

3 files changed

+106
-53
lines changed

3 files changed

+106
-53
lines changed

src/vs/workbench/browser/quickaccess.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/con
88
import { ICommandHandler } from 'vs/platform/commands/common/commands';
99
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1010
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
11+
import { getIEditor } from 'vs/editor/browser/editorBrowser';
12+
import { ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
13+
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
14+
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
15+
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
16+
import { IEditorOptions } from 'vs/platform/editor/common/editor';
1117

1218
export const inQuickPickContextKeyValue = 'inQuickOpen';
1319
export const InQuickPickContextKey = new RawContextKey<boolean>(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
4551
quickInputService.navigate(!!next, quickNavigate);
4652
};
4753
}
54+
export class EditorViewState {
55+
private _editorViewState: {
56+
editor: EditorInput;
57+
group: IEditorGroup;
58+
state: ICodeEditorViewState | IDiffEditorViewState | undefined;
59+
} | undefined = undefined;
60+
61+
constructor(private readonly editorService: IEditorService) { }
62+
63+
set(): void {
64+
if (this._editorViewState) {
65+
return; // return early if already done
66+
}
67+
68+
const activeEditorPane = this.editorService.activeEditorPane;
69+
if (activeEditorPane) {
70+
this._editorViewState = {
71+
group: activeEditorPane.group,
72+
editor: activeEditorPane.input,
73+
state: getIEditor(activeEditorPane.getControl())?.saveViewState() ?? undefined,
74+
};
75+
}
76+
}
77+
78+
async restore(): Promise<void> {
79+
if (this._editorViewState) {
80+
const options: IEditorOptions = {
81+
viewState: this._editorViewState.state,
82+
preserveFocus: true /* import to not close the picker as a result */
83+
};
84+
85+
await this._editorViewState.group.openEditor(this._editorViewState.editor, options);
86+
}
87+
}
88+
89+
reset() {
90+
this._editorViewState = undefined;
91+
}
92+
}

src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,17 @@ import { ThrottledDelayer } from 'vs/base/common/async';
3535
import { top } from 'vs/base/common/arrays';
3636
import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState';
3737
import { IHistoryService } from 'vs/workbench/services/history/common/history';
38-
import { IEditorOptions, IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor';
38+
import { IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor';
3939
import { Schemas } from 'vs/base/common/network';
4040
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
4141
import { ResourceMap } from 'vs/base/common/map';
4242
import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess';
4343
import { AnythingQuickAccessProviderRunOptions, DefaultQuickAccessFilterValue, Extensions, IQuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess';
44-
import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess';
44+
import { EditorViewState, IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess';
4545
import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess';
4646
import { ITextModelService } from 'vs/editor/common/services/resolverService';
47-
import { ScrollType, IEditor, ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
47+
import { ScrollType, IEditor } from 'vs/editor/common/editorCommon';
4848
import { Event } from 'vs/base/common/event';
49-
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
50-
import { getIEditor } from 'vs/editor/browser/editorBrowser';
5149
import { Codicon } from 'vs/base/common/codicons';
5250
import { ThemeIcon } from 'vs/base/common/themables';
5351
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
@@ -89,11 +87,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
8987

9088
picker: IQuickPick<IAnythingQuickPickItem> | undefined = undefined;
9189

92-
editorViewState: {
93-
editor: EditorInput;
94-
group: IEditorGroup;
95-
state: ICodeEditorViewState | IDiffEditorViewState | undefined;
96-
} | undefined = undefined;
90+
editorViewState: EditorViewState;
9791

9892
scorerCache: FuzzyScorerCache = Object.create(null);
9993
fileQueryCache: FileQueryCacheState | undefined = undefined;
@@ -106,7 +100,9 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
106100

107101
isQuickNavigating: boolean | undefined = undefined;
108102

109-
constructor(private readonly provider: AnythingQuickAccessProvider, private readonly editorService: IEditorService) { }
103+
constructor(private readonly provider: AnythingQuickAccessProvider, editorService: IEditorService) {
104+
this.editorViewState = new EditorViewState(editorService);
105+
}
110106

111107
set(picker: IQuickPick<IAnythingQuickPickItem>): void {
112108

@@ -131,33 +127,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
131127
this.lastFilter = undefined;
132128
this.lastRange = undefined;
133129
this.lastGlobalPicks = undefined;
134-
this.editorViewState = undefined;
135-
}
136-
137-
rememberEditorViewState(): void {
138-
if (this.editorViewState) {
139-
return; // return early if already done
140-
}
141-
142-
const activeEditorPane = this.editorService.activeEditorPane;
143-
if (activeEditorPane) {
144-
this.editorViewState = {
145-
group: activeEditorPane.group,
146-
editor: activeEditorPane.input,
147-
state: getIEditor(activeEditorPane.getControl())?.saveViewState() ?? undefined,
148-
};
149-
}
150-
}
151-
152-
async restoreEditorViewState(): Promise<void> {
153-
if (this.editorViewState) {
154-
const options: IEditorOptions = {
155-
viewState: this.editorViewState.state,
156-
preserveFocus: true /* import to not close the picker as a result */
157-
};
158-
159-
await this.editorViewState.group.openEditor(this.editorViewState.editor, options);
160-
}
130+
this.editorViewState.reset();
161131
}
162132
}(this, this.editorService);
163133

@@ -237,7 +207,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
237207
// could mean the user clicked into the editor directly.
238208
disposables.add(Event.once(picker.onDidHide)(({ reason }) => {
239209
if (reason === QuickInputHideReason.Gesture) {
240-
this.pickState.restoreEditorViewState();
210+
this.pickState.editorViewState.restore();
241211
}
242212
}));
243213

@@ -259,7 +229,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
259229
}
260230

261231
// we must remember our curret view state to be able to restore
262-
this.pickState.rememberEditorViewState();
232+
this.pickState.editorViewState.set();
263233

264234
// Reveal
265235
activeEditorControl.revealRangeInCenter(pick.range.selection, ScrollType.Smooth);
@@ -871,7 +841,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
871841
try {
872842

873843
// we must remember our curret view state to be able to restore
874-
this.pickState.rememberEditorViewState();
844+
this.pickState.editorViewState.set();
875845

876846
// open it
877847
await this.editorService.openEditor({
@@ -1039,7 +1009,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
10391009

10401010
// Restore any view state if the target is the side group
10411011
if (targetGroup === SIDE_GROUP) {
1042-
await this.pickState.restoreEditorViewState();
1012+
await this.pickState.editorViewState.restore();
10431013
}
10441014

10451015
// Open editor (typed)

src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ import { ILabelService } from 'vs/platform/label/common/label';
1717
import { WorkbenchCompressibleObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService';
1818
import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
1919
import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess';
20-
import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
20+
import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput';
2121
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
2222
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
23-
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
2423
import { searchDetailsIcon, searchOpenInFileIcon, searchActivityBarIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
2524
import { FileMatch, Match, RenderableMatch, SearchModel, SearchModelLocation, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel';
2625
import { SearchView, getEditorSelectionFromMatch } from 'vs/workbench/contrib/search/browser/searchView';
2726
import { IWorkbenchSearchConfiguration, getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search';
2827
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
2928
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
3029
import { IPatternInfo, ISearchComplete, ITextQuery, VIEW_ID } from 'vs/workbench/services/search/common/search';
30+
import { Event } from 'vs/base/common/event';
31+
import { EditorViewState } from 'vs/workbench/browser/quickaccess';
32+
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
3133

3234
export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%';
3335

@@ -42,13 +44,20 @@ const DEFAULT_TEXT_QUERY_BUILDER_OPTIONS: ITextQueryBuilderOptions = {
4244
const MAX_FILES_SHOWN = 30;
4345
const MAX_RESULTS_PER_FILE = 10;
4446

45-
export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
47+
interface ITextSearchQuickAccessItem extends IPickerQuickAccessItem {
48+
match?: Match;
49+
}
50+
export class TextSearchQuickAccess extends PickerQuickAccessProvider<ITextSearchQuickAccessItem> {
4651
private queryBuilder: QueryBuilder;
4752
private searchModel: SearchModel;
4853
private currentAsyncSearch: Promise<ISearchComplete> = Promise.resolve({
4954
results: [],
5055
messages: []
5156
});
57+
private storedOriginalLocation = false;
58+
private readonly editorViewState = new EditorViewState(
59+
this._editorService
60+
);
5261

5362
private _getTextQueryBuilderOptions(charsPerLine: number): ITextQueryBuilderOptions {
5463
return {
@@ -72,7 +81,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
7281
@IEditorService private readonly _editorService: IEditorService,
7382
@ILabelService private readonly _labelService: ILabelService,
7483
@IViewsService private readonly _viewsService: IViewsService,
75-
@IConfigurationService private readonly _configurationService: IConfigurationService,
84+
@IConfigurationService private readonly _configurationService: IConfigurationService
7685
) {
7786
super(TEXT_SEARCH_QUICK_ACCESS_PREFIX, { canAcceptInBackground: true, shouldSkipTrimPickFilter: true });
7887

@@ -86,23 +95,51 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
8695
super.dispose();
8796
}
8897

89-
override provide(picker: IQuickPick<IPickerQuickAccessItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
98+
override provide(picker: IQuickPick<ITextSearchQuickAccessItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
9099
const disposables = new DisposableStore();
91100
if (TEXT_SEARCH_QUICK_ACCESS_PREFIX.length < picker.value.length) {
92101
picker.valueSelection = [TEXT_SEARCH_QUICK_ACCESS_PREFIX.length, picker.value.length];
93102
}
94103
picker.customButton = true;
95104
picker.customLabel = '$(link-external)';
96-
picker.onDidCustom(() => {
105+
disposables.add(picker.onDidCustom(() => {
97106
if (this.searchModel.searchResult.count() > 0) {
98107
this.moveToSearchViewlet(undefined);
99108
} else {
100109
this._viewsService.openView(VIEW_ID, true);
101110
}
102111
picker.hide();
103-
});
112+
}));
113+
disposables.add(picker.onDidChangeActive(() => {
114+
const [item] = picker.activeItems;
115+
116+
if (item?.match) {
117+
// only store location once, or else it will store new state every time we change active pick
118+
if (!this.storedOriginalLocation) {
119+
// we must remember our curret view state to be able to restore
120+
this.editorViewState.set();
121+
this.storedOriginalLocation = true;
122+
}
123+
// open it
124+
this._editorService.openEditor({
125+
resource: item.match.parent().resource,
126+
options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: item.match.range() }
127+
});
128+
}
129+
}));
130+
131+
disposables.add(Event.once(picker.onDidHide)(({ reason }) => {
132+
// Restore view state upon cancellation if we changed it
133+
// but only when the picker was closed via explicit user
134+
// gesture and not e.g. when focus was lost because that
135+
// could mean the user clicked into the editor directly.
136+
if (reason === QuickInputHideReason.Gesture) {
137+
this.editorViewState.restore();
138+
}
139+
this.searchModel.searchResult.toggleHighlights(false);
140+
}));
141+
104142
disposables.add(super.provide(picker, token, runOptions));
105-
disposables.add(picker.onDidHide(() => this.searchModel.searchResult.toggleHighlights(false)));
106143
disposables.add(picker.onDidAccept(() => this.searchModel.searchResult.toggleHighlights(false)));
107144
return disposables;
108145
}
@@ -177,11 +214,11 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
177214
}
178215
}
179216

180-
private _getPicksFromMatches(matches: FileMatch[], limit: number): (IQuickPickSeparator | IPickerQuickAccessItem)[] {
217+
private _getPicksFromMatches(matches: FileMatch[], limit: number): (IQuickPickSeparator | ITextSearchQuickAccessItem)[] {
181218
matches = matches.sort(searchComparer);
182219

183220
const files = matches.length > limit ? matches.slice(0, limit) : matches;
184-
const picks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
221+
const picks: Array<ITextSearchQuickAccessItem | IQuickPickSeparator> = [];
185222

186223
for (let fileIndex = 0; fileIndex < matches.length; fileIndex++) {
187224
if (fileIndex === limit) {
@@ -258,7 +295,8 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
258295
trigger: (): TriggerAction => {
259296
this.moveToSearchViewlet(element);
260297
return TriggerAction.CLOSE_PICKER;
261-
}
298+
},
299+
match: element
262300
});
263301
}
264302
}

0 commit comments

Comments
 (0)