Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement preview on highlighting quickpick in quick search #202306

Merged
merged 9 commits into from
Jan 18, 2024
45 changes: 45 additions & 0 deletions src/vs/workbench/browser/quickaccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(inQuickPickContextKeyValue, false, localize('inQuickOpen', "Whether keyboard focus is inside the quick open control"));
Expand Down Expand Up @@ -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<void> {
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;
}
}
54 changes: 12 additions & 42 deletions src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -89,11 +87,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt

picker: IQuickPick<IAnythingQuickPickItem> | undefined = undefined;

editorViewState: {
editor: EditorInput;
group: IEditorGroup;
state: ICodeEditorViewState | IDiffEditorViewState | undefined;
} | undefined = undefined;
editorViewState: EditorViewState;

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

isQuickNavigating: boolean | undefined = undefined;

constructor(private readonly provider: AnythingQuickAccessProvider, private readonly editorService: IEditorService) { }
constructor(private readonly provider: AnythingQuickAccessProvider, editorService: IEditorService) {
this.editorViewState = new EditorViewState(editorService);
}

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

Expand All @@ -131,33 +127,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
this.lastFilter = undefined;
this.lastRange = undefined;
this.lastGlobalPicks = undefined;
this.editorViewState = undefined;
}

rememberEditorViewState(): 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 restoreEditorViewState(): Promise<void> {
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);

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

Expand All @@ -259,7 +229,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
}

// we must remember our curret view state to be able to restore
this.pickState.rememberEditorViewState();
this.pickState.editorViewState.set();

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

// we must remember our curret view state to be able to restore
this.pickState.rememberEditorViewState();
this.pickState.editorViewState.set();

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

// Restore any view state if the target is the side group
if (targetGroup === SIDE_GROUP) {
await this.pickState.restoreEditorViewState();
await this.pickState.editorViewState.restore();
}

// Open editor (typed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { WorkbenchCompressibleObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService';
import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess';
import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
Expand All @@ -28,6 +28,8 @@ import { IWorkbenchSearchConfiguration, getOutOfWorkspaceEditorResources } from
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IPatternInfo, ITextQuery, VIEW_ID } from 'vs/workbench/services/search/common/search';
import { Event } from 'vs/base/common/event';
import { EditorViewState } from 'vs/workbench/browser/quickaccess';

export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%';

Expand All @@ -42,9 +44,16 @@ const DEFAULT_TEXT_QUERY_BUILDER_OPTIONS: ITextQueryBuilderOptions = {
const MAX_FILES_SHOWN = 30;
const MAX_RESULTS_PER_FILE = 10;

export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
interface ITextSearchQuickAccessItem extends IPickerQuickAccessItem {
match?: Match;
}
export class TextSearchQuickAccess extends PickerQuickAccessProvider<ITextSearchQuickAccessItem> {
private queryBuilder: QueryBuilder;
private searchModel: SearchModel;
private storedOriginalLocation = false;
private readonly editorViewState = new EditorViewState(
this._editorService
);

private _getTextQueryBuilderOptions(charsPerLine: number): ITextQueryBuilderOptions {
return {
Expand All @@ -68,7 +77,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
@IEditorService private readonly _editorService: IEditorService,
@ILabelService private readonly _labelService: ILabelService,
@IViewsService private readonly _viewsService: IViewsService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super(TEXT_SEARCH_QUICK_ACCESS_PREFIX, { canAcceptInBackground: true, shouldSkipTrimPickFilter: true });

Expand All @@ -81,19 +90,47 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
super.dispose();
}

override provide(picker: IQuickPick<IPickerQuickAccessItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
override provide(picker: IQuickPick<ITextSearchQuickAccessItem>, 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(() => {
this.moveToSearchViewlet(this.searchModel, undefined);
picker.hide();
});
}));
disposables.add(picker.onDidChangeActive(() => {
const [item] = picker.activeItems;

if (item && item.match) {
andreamah marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
Expand Down Expand Up @@ -164,11 +201,11 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
}
}

private _getPicksFromMatches(matches: FileMatch[], limit: number): (IQuickPickSeparator | IPickerQuickAccessItem)[] {
private _getPicksFromMatches(matches: FileMatch[], limit: number): (IQuickPickSeparator | ITextSearchQuickAccessItem)[] {
matches = matches.sort(searchComparer);

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

for (let fileIndex = 0; fileIndex < matches.length; fileIndex++) {
if (fileIndex === limit) {
Expand Down Expand Up @@ -245,7 +282,8 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
trigger: (): TriggerAction => {
this.moveToSearchViewlet(this.searchModel, element);
return TriggerAction.CLOSE_PICKER;
}
},
match: element
});
}
}
Expand Down
Loading