Skip to content

Commit 54bccc2

Browse files
author
Benjamin Pasero
committed
quick access - fix various remaining issues:
- run additional file search over original query if limit hit and multiple inputs - preserve existing highlights of symbols - reduce flicker when having fast and slow results
1 parent 66f0e65 commit 54bccc2

7 files changed

Lines changed: 128 additions & 85 deletions

File tree

src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
156156
// Collect symbol picks
157157
picker.busy = true;
158158
try {
159-
const items = await this.doGetSymbolPicks(symbolsPromise, prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim()), picksCts.token);
159+
const items = await this.doGetSymbolPicks(symbolsPromise, prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim()), undefined, picksCts.token);
160160
if (token.isCancellationRequested) {
161161
return;
162162
}
@@ -195,7 +195,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
195195
return disposables;
196196
}
197197

198-
protected async doGetSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, query: IPreparedQuery, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
198+
protected async doGetSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, query: IPreparedQuery, options: { extraContainerLabel?: string } | undefined, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
199199
const symbols = await symbolsPromise;
200200
if (token.isCancellationRequested) {
201201
return [];
@@ -220,7 +220,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
220220
const symbol = symbols[index];
221221

222222
const symbolLabel = trim(symbol.name);
223-
const containerLabel = symbol.containerName;
223+
224+
let containerLabel = symbol.containerName;
225+
if (containerLabel && options?.extraContainerLabel) {
226+
containerLabel = `${options.extraContainerLabel}${containerLabel}`;
227+
} else {
228+
containerLabel = options?.extraContainerLabel;
229+
}
224230

225231
let symbolScore: FuzzyScore | undefined = undefined;
226232
let containerScore: FuzzyScore | undefined = undefined;

src/vs/platform/product/common/product.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if (isWeb) {
2121
// Running out of sources
2222
if (Object.keys(product).length === 0) {
2323
assign(product, {
24-
version: '1.43.0-dev',
24+
version: '1.44.0-dev',
2525
nameLong: 'Visual Studio Code Web Dev',
2626
nameShort: 'VSCode Web Dev',
2727
urlProtocol: 'code-oss'

src/vs/platform/quickinput/browser/pickerQuickAccess.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,27 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
115115
const picksToken = picksCts.token;
116116
const providedPicks = this.getPicks(picker.value.substr(this.prefix.length).trim(), picksDisposables, picksToken);
117117

118-
function applyPicks(picks: Picks<T>): void {
118+
function applyPicks(picks: Picks<T>, skipEmpty?: boolean): boolean {
119+
let items: ReadonlyArray<Pick<T>>;
120+
let activeItem: T | undefined = undefined;
121+
119122
if (isPicksWithActive(picks)) {
120-
picker.items = picks.items;
121-
if (picks.active) {
122-
picker.activeItems = [picks.active];
123-
}
123+
items = picks.items;
124+
activeItem = picks.active;
124125
} else {
125-
picker.items = picks;
126+
items = picks;
127+
}
128+
129+
if (items.length === 0 && skipEmpty) {
130+
return false;
126131
}
132+
133+
picker.items = items;
134+
if (activeItem) {
135+
picker.activeItems = [activeItem];
136+
}
137+
138+
return true;
127139
}
128140

129141
// No Picks
@@ -133,8 +145,8 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
133145

134146
// Fast and Slow Picks
135147
else if (isFastAndSlowPicks(providedPicks)) {
136-
let fastPicksHandlerDone = false;
137-
let slowPicksHandlerDone = false;
148+
let fastPicksApplied = false;
149+
let slowPicksApplied = false;
138150

139151
await Promise.all([
140152

@@ -143,17 +155,13 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
143155
// If the slow picks are faster, we reduce the flicker by
144156
// only setting the items once.
145157
(async () => {
146-
try {
147-
await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY);
148-
if (picksToken.isCancellationRequested) {
149-
return;
150-
}
158+
await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY);
159+
if (picksToken.isCancellationRequested) {
160+
return;
161+
}
151162

152-
if (!slowPicksHandlerDone) {
153-
applyPicks(providedPicks.picks);
154-
}
155-
} finally {
156-
fastPicksHandlerDone = true;
163+
if (!slowPicksApplied) {
164+
fastPicksApplied = applyPicks(providedPicks.picks, true /* skip over empty to reduce flicker */);
157165
}
158166
})(),
159167

@@ -186,7 +194,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
186194
additionalPicks = awaitedAdditionalPicks;
187195
}
188196

189-
if (additionalPicks.length > 0 || !fastPicksHandlerDone) {
197+
if (additionalPicks.length > 0 || !fastPicksApplied) {
190198
applyPicks({
191199
items: [...picks, ...additionalPicks],
192200
active: activePick || additionalActivePick
@@ -197,7 +205,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
197205
picker.busy = false;
198206
}
199207

200-
slowPicksHandlerDone = true;
208+
slowPicksApplied = true;
201209
}
202210
})()
203211
]);

src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,6 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
269269
}
270270
}
271271

272-
return false;
272+
return false; // no veto (no dirty)
273273
}
274274
}

src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
7171

7272
private static readonly SYMBOL_PICKS_TIMEOUT = 8000;
7373

74-
async getSymbolPicks(model: ITextModel, filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
74+
async getSymbolPicks(model: ITextModel, filter: string, options: { extraContainerLabel?: string }, disposables: DisposableStore, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
7575

7676
// If the registry does not know the model, we wait for as long as
7777
// the registry knows it. This helps in cases where a language
@@ -86,7 +86,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
8686
return [];
8787
}
8888

89-
return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), prepareQuery(filter), token);
89+
return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), prepareQuery(filter), options, token);
9090
}
9191

9292
addDecorations(editor: IEditor, range: IRange): void {

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

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { prepareQuery, IPreparedQuery, compareItemsByScore, scoreItem, ScorerCac
1010
import { IFileQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
1111
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1212
import { getOutOfWorkspaceEditorResources, extractRangeFromFilter, IWorkbenchSearchConfiguration } from 'vs/workbench/contrib/search/common/search';
13-
import { ISearchService } from 'vs/workbench/services/search/common/search';
13+
import { ISearchService, ISearchComplete } from 'vs/workbench/services/search/common/search';
1414
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
1515
import { untildify } from 'vs/base/common/labels';
1616
import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService';
@@ -48,6 +48,7 @@ import { once } from 'vs/base/common/functional';
4848
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
4949
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
5050
import { withNullAsUndefined } from 'vs/base/common/types';
51+
import { stripCodicons } from 'vs/base/common/codicons';
5152

5253
interface IAnythingQuickPickItem extends IPickerQuickAccessItem {
5354
resource: URI | undefined;
@@ -367,17 +368,26 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
367368
// Perform filtering
368369
const filteredAnythingPicks: IAnythingQuickPickItem[] = [];
369370
for (const anythingPick of sortedAnythingPicks) {
370-
const { score, labelMatch, descriptionMatch } = scoreItem(anythingPick, query, true, quickPickItemScorerAccessor, this.pickState.scorerCache);
371-
if (!score) {
372-
continue; // exclude files/symbols not matching query
371+
372+
// Always preserve any existing highlights (e.g. from workspace symbols)
373+
if (anythingPick.highlights) {
374+
filteredAnythingPicks.push(anythingPick);
373375
}
374376

375-
anythingPick.highlights = {
376-
label: labelMatch,
377-
description: descriptionMatch
378-
};
377+
// Otherwise, do the scoring and matching here
378+
else {
379+
const { score, labelMatch, descriptionMatch } = scoreItem(anythingPick, query, true, quickPickItemScorerAccessor, this.pickState.scorerCache);
380+
if (!score) {
381+
continue;
382+
}
383+
384+
anythingPick.highlights = {
385+
label: labelMatch,
386+
description: descriptionMatch
387+
};
379388

380-
filteredAnythingPicks.push(anythingPick);
389+
filteredAnythingPicks.push(anythingPick);
390+
}
381391
}
382392

383393
return filteredAnythingPicks;
@@ -498,15 +508,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
498508
const [fileSearchResults, relativePathFileResults] = await Promise.all([
499509

500510
// File search: this is a search over all files of the workspace using the provided pattern
501-
this.searchService.fileSearch(
502-
this.fileQueryBuilder.file(
503-
this.contextService.getWorkspace().folders,
504-
this.getFileQueryOptions({
505-
query,
506-
cacheKey: this.pickState.fileQueryCache?.cacheKey,
507-
maxResults: AnythingQuickAccessProvider.MAX_RESULTS
508-
})
509-
), token),
511+
this.getFileSearchResults(query, token),
510512

511513
// Relative path search: we also want to consider results that match files inside the workspace
512514
// by looking for relative paths that the user typed as query. This allows to return even excluded
@@ -521,7 +523,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
521523

522524
// Return quickly if no relative results are present
523525
if (!relativePathFileResults) {
524-
return fileSearchResults.results.map(result => result.resource);
526+
return fileSearchResults;
525527
}
526528

527529
// Otherwise, make sure to filter relative path results from
@@ -532,12 +534,12 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
532534
}
533535

534536
return [
535-
...fileSearchResults.results.filter(result => !relativePathFileResultsMap.has(result.resource)).map(result => result.resource),
537+
...fileSearchResults.filter(result => !relativePathFileResultsMap.has(result)),
536538
...relativePathFileResults
537539
];
538540
}
539541

540-
private getFileQueryOptions(input: { query?: IPreparedQuery, cacheKey?: string, maxResults?: number }): IFileQueryBuilderOptions {
542+
private async getFileSearchResults(query: IPreparedQuery, token: CancellationToken): Promise<URI[]> {
541543

542544
// filePattern for search depends on the number of queries in input:
543545
// - with multiple: only take the first one and let the filter later drop non-matching results
@@ -547,18 +549,61 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
547549
// search results for "someFile" and not both that would normally not match.
548550
//
549551
let filePattern = '';
550-
if (input.query) {
551-
if (input.query.values && input.query.values.length > 1) {
552-
filePattern = input.query.values[0].original;
553-
} else {
554-
filePattern = input.query.original;
552+
if (query.values && query.values.length > 1) {
553+
filePattern = query.values[0].original;
554+
} else {
555+
filePattern = query.original;
556+
}
557+
558+
const fileSearchResults = await this.doGetFileSearchResults(filePattern, token);
559+
if (token.isCancellationRequested) {
560+
return [];
561+
}
562+
563+
// If we detect that the search limit has been hit and we have a query
564+
// that was composed of multiple inputs where we only took the first part
565+
// we run another search with the full original query included to make
566+
// sure we are including all possible results that could match.
567+
if (fileSearchResults.limitHit && query.values && query.values.length > 1) {
568+
const additionalFileSearchResults = await this.doGetFileSearchResults(query.original, token);
569+
if (token.isCancellationRequested) {
570+
return [];
571+
}
572+
573+
// Remember which result we already covered
574+
const existingFileSearchResultsMap = new ResourceMap<boolean>();
575+
for (const fileSearchResult of fileSearchResults.results) {
576+
existingFileSearchResultsMap.set(fileSearchResult.resource, true);
577+
}
578+
579+
// Add all additional results to the original set for inclusion
580+
for (const additionalFileSearchResult of additionalFileSearchResults.results) {
581+
if (!existingFileSearchResultsMap.has(additionalFileSearchResult.resource)) {
582+
fileSearchResults.results.push(additionalFileSearchResult);
583+
}
555584
}
556585
}
557586

587+
return fileSearchResults.results.map(result => result.resource);
588+
}
589+
590+
private doGetFileSearchResults(filePattern: string, token: CancellationToken): Promise<ISearchComplete> {
591+
return this.searchService.fileSearch(
592+
this.fileQueryBuilder.file(
593+
this.contextService.getWorkspace().folders,
594+
this.getFileQueryOptions({
595+
filePattern,
596+
cacheKey: this.pickState.fileQueryCache?.cacheKey,
597+
maxResults: AnythingQuickAccessProvider.MAX_RESULTS
598+
})
599+
), token);
600+
}
601+
602+
private getFileQueryOptions(input: { filePattern?: string, cacheKey?: string, maxResults?: number }): IFileQueryBuilderOptions {
558603
return {
559604
_reason: 'openFileHandler', // used for telemetry - do not change
560605
extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources),
561-
filePattern,
606+
filePattern: input.filePattern || '',
562607
cacheKey: input.cacheKey,
563608
maxResults: input.maxResults || 0,
564609
sortByScore: true
@@ -570,7 +615,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
570615
return;
571616
}
572617

573-
const detildifiedQuery = untildify(query.value, (await this.remotePathService.userHome).path);
618+
const detildifiedQuery = untildify(query.original, (await this.remotePathService.userHome).path);
574619
if (token.isCancellationRequested) {
575620
return;
576621
}
@@ -609,7 +654,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
609654

610655
// Convert relative paths to absolute paths over all folders of the workspace
611656
// and return them as results if the absolute paths exist
612-
const isAbsolutePathQuery = (await this.remotePathService.path).isAbsolute(query.value);
657+
const isAbsolutePathQuery = (await this.remotePathService.path).isAbsolute(query.original);
613658
if (!isAbsolutePathQuery) {
614659
const resources: URI[] = [];
615660
for (const folder of this.contextService.getWorkspace().folders) {
@@ -618,7 +663,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
618663
}
619664

620665
const resource = toLocalResource(
621-
folder.toResource(query.value),
666+
folder.toResource(query.original),
622667
this.environmentService.configuration.remoteAuthority
623668
);
624669

@@ -654,25 +699,11 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
654699
return [];
655700
}
656701

657-
// symbolPattern for search depends on the number of queries in input:
658-
// - with multiple: only take the first one and let the filter later drop non-matching results
659-
// - with single: just take the original in full
660-
//
661-
// This enables to e.g. search for "someFile someFolder" by only returning
662-
// symbol results for "someFile" and not both that would normally not match.
663-
//
664-
let symbolPattern = '';
665-
if (query.values && query.values.length > 1) {
666-
symbolPattern = query.values[0].original;
667-
} else {
668-
symbolPattern = query.original;
669-
}
670-
671702
// Delegate to the existing symbols quick access
672703
// but skip local results and also do not score
673-
return this.workspaceSymbolsQuickAccess.getSymbolPicks(symbolPattern, {
704+
return this.workspaceSymbolsQuickAccess.getSymbolPicks(query.original, {
674705
skipLocal: true,
675-
skipScoring: true,
706+
skipSorting: true,
676707
delay: AnythingQuickAccessProvider.TYPING_SEARCH_DELAY
677708
}, token);
678709
}
@@ -740,7 +771,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
740771
}
741772

742773
// Ask provider for editor symbols
743-
const editorSymbolPicks = (await this.editorSymbolsQuickAccess.getSymbolPicks(model, filter, disposables, token));
774+
const editorSymbolPicks = (await this.editorSymbolsQuickAccess.getSymbolPicks(model, filter, { extraContainerLabel: stripCodicons(activeGlobalPick.label) }, disposables, token));
744775
if (token.isCancellationRequested) {
745776
return [];
746777
}
@@ -756,7 +787,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
756787
return {
757788
...editorSymbolPick,
758789
resource: activeGlobalResource,
759-
description: editorSymbolPick.description ? `${activeGlobalPick.label}${editorSymbolPick.description}` : activeGlobalPick.label,
790+
description: editorSymbolPick.description,
760791
trigger: (buttonIndex, keyMods) => {
761792
this.openAnything(activeGlobalResource, { keyMods, range: editorSymbolPick.range?.selection, forceOpenSideBySide: true });
762793

0 commit comments

Comments
 (0)