Skip to content

Commit

Permalink
quick access - allow a Promise<FastAndSlowPicks<T>> and adopt for c…
Browse files Browse the repository at this point in the history
…ommands (#180664)

* quick access - allow a `Promise<FastAndSlowPicks<T>>` and adopt for commands

* fix telemetry
  • Loading branch information
bpasero authored Apr 24, 2023
1 parent 25da7d5 commit 3a69e15
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 91 deletions.
2 changes: 1 addition & 1 deletion extensions/microsoft-authentication/src/AADHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class AzureActiveDirectoryService {
"login" : {
"owner": "TylerLeonhardt",
"comment": "Used to determine the usage of the Microsoft Auth Provider.",
"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." },
"accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." }
}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { alert } from 'vs/base/browser/ui/aria/aria';

interface IEditorLineDecoration {
rangeHighlightId: string;
overviewRulerDecorationId: string;
readonly rangeHighlightId: string;
readonly overviewRulerDecorationId: string;
}

export interface IEditorNavigationQuickAccessOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommand
super({ showAlias: false }, instantiationService, keybindingService, commandService, telemetryService, dialogService);
}

protected getCommandPicks(): Array<ICommandQuickPick> {
protected async getCommandPicks(): Promise<Array<ICommandQuickPick>> {
return this.getCodeEditorCommandPicks();
}

protected hasAdditionalCommandPicks(): boolean {
return false;
}

protected async getAdditionalCommandPicks(): Promise<ICommandQuickPick[]> {
return [];
}
Expand Down
46 changes: 26 additions & 20 deletions src/vs/platform/quickinput/browser/commandsQuickAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

export interface ICommandQuickPick extends IPickerQuickAccessItem {
commandId: string;
commandAlias?: string;
readonly commandId: string;
readonly commandAlias?: string;
}

export interface ICommandsQuickAccessOptions extends IPickerQuickAccessProviderOptions<ICommandQuickPick> {
showAlias: boolean;
readonly showAlias: boolean;
suggestedCommandIds?: Set<string>;
}

Expand All @@ -56,10 +56,10 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
this.options = options;
}

protected _getPicks(filter: string, _disposables: DisposableStore, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): Picks<ICommandQuickPick> | FastAndSlowPicks<ICommandQuickPick> {
protected async _getPicks(filter: string, _disposables: DisposableStore, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): Promise<Picks<ICommandQuickPick> | FastAndSlowPicks<ICommandQuickPick>> {

// Ask subclass for all command picks
const allCommandPicks = this.getCommandPicks(token);
const allCommandPicks = await this.getCommandPicks(token);

if (token.isCancellationRequested) {
return [];
Expand Down Expand Up @@ -166,17 +166,28 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
commandPicks.push(this.toCommandPick(commandPick, runOptions));
}

if (!this.hasAdditionalCommandPicks(filter, token)) {
return commandPicks;
}

return {
picks: commandPicks,
additionalPicks: this.getAdditionalCommandPicks(allCommandPicks, filteredCommandPicks, filter, token)
.then(additionalCommandPicks => additionalCommandPicks.map(commandPick => this.toCommandPick(commandPick, runOptions)))
additionalPicks: (async (): Promise<Picks<ICommandQuickPick>> => {
const additionalCommandPicks = await this.getAdditionalCommandPicks(allCommandPicks, filteredCommandPicks, filter, token);
if (token.isCancellationRequested) {
return [];
}

return additionalCommandPicks.map(commandPick => this.toCommandPick(commandPick, runOptions));
})()
};
}

private toCommandPick(commandPick: ICommandQuickPick | IQuickPickSeparator, runOptions?: IQuickAccessProviderRunOptions): ICommandQuickPick | IQuickPickSeparator {
if (commandPick.type === 'separator') {
return commandPick;
}

const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId);
const ariaLabel = keybinding ?
localize('commandPickAriaLabelWithKeybinding', "{0}, {1}", commandPick.label, keybinding.getAriaLabel()) :
Expand Down Expand Up @@ -210,27 +221,22 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
};
}

/**
* Subclasses to provide the actual command entries.
*/
protected abstract getCommandPicks(token: CancellationToken): Array<ICommandQuickPick>;
protected abstract getCommandPicks(token: CancellationToken): Promise<Array<ICommandQuickPick>>;

/**
* Subclasses to provide the actual command entries.
*/
protected abstract hasAdditionalCommandPicks(filter: string, token: CancellationToken): boolean;
protected abstract getAdditionalCommandPicks(allPicks: ICommandQuickPick[], picksSoFar: ICommandQuickPick[], filter: string, token: CancellationToken): Promise<Array<ICommandQuickPick | IQuickPickSeparator>>;
}

interface ISerializedCommandHistory {
usesLRU?: boolean;
entries: { key: string; value: number }[];
readonly usesLRU?: boolean;
readonly entries: { key: string; value: number }[];
}

interface ICommandsQuickAccessConfiguration {
workbench: {
commandPalette: {
history: number;
preserveInput: boolean;
readonly workbench: {
readonly commandPalette: {
readonly history: number;
readonly preserveInput: boolean;
};
};
}
Expand Down
3 changes: 1 addition & 2 deletions src/vs/platform/quickinput/browser/helpQuickAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Extensions, IQuickAccessProvider, IQuickAccessProviderDescriptor, IQuic
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';

interface IHelpQuickAccessPickItem extends IQuickPickItem {
prefix: string;
readonly prefix: string;
}

export class HelpQuickAccessProvider implements IQuickAccessProvider {
Expand Down Expand Up @@ -75,4 +75,3 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
});
}
}

84 changes: 57 additions & 27 deletions src/vs/platform/quickinput/browser/pickerQuickAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,37 @@ export interface IPickerQuickAccessProviderOptions<T extends IPickerQuickAccessI
/**
* Enables support for opening picks in the background via gesture.
*/
canAcceptInBackground?: boolean;
readonly canAcceptInBackground?: boolean;

/**
* Enables to show a pick entry when no results are returned from a search.
*/
noResultsPick?: T | ((filter: string) => T);
readonly noResultsPick?: T | ((filter: string) => T);
}

export type Pick<T> = T | IQuickPickSeparator;
export type PicksWithActive<T> = { items: readonly Pick<T>[]; active?: T };
export type Picks<T> = readonly Pick<T>[] | PicksWithActive<T>;
export type FastAndSlowPicks<T> = { picks: Picks<T>; additionalPicks: Promise<Picks<T>> };
export type FastAndSlowPicks<T> = {

/**
* Picks that will show instantly or after a short delay
* based on the `mergeDelay` property to reduce flicker.
*/
readonly picks: Picks<T>;

/**
* Picks that will show after they have been resolved.
*/
readonly additionalPicks: Promise<Picks<T>>;

/**
* A delay in milliseconds to wait before showing the
* `picks` to give a chance to merge with `additionalPicks`
* for reduced flicker.
*/
readonly mergeDelay?: number;
};

function isPicksWithActive<T>(obj: unknown): obj is PicksWithActive<T> {
const candidate = obj as PicksWithActive<T>;
Expand All @@ -91,8 +110,6 @@ function isFastAndSlowPicks<T>(obj: unknown): obj is FastAndSlowPicks<T> {

export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem> extends Disposable implements IQuickAccessProvider {

private static FAST_PICKS_RACE_DELAY = 200; // timeout before we accept fast results before slow results are present

constructor(private prefix: string, protected options?: IPickerQuickAccessProviderOptions<T>) {
super();
}
Expand Down Expand Up @@ -158,51 +175,50 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
return true;
};

// No Picks
if (providedPicks === null) {
// Ignore
}

// Fast and Slow Picks
else if (isFastAndSlowPicks(providedPicks)) {
const applyFastAndSlowPicks = async (fastAndSlowPicks: FastAndSlowPicks<T>): Promise<void> => {
let fastPicksApplied = false;
let slowPicksApplied = false;

await Promise.all([

// Fast Picks: to reduce amount of flicker, we race against
// the slow picks over 500ms and then set the fast picks.
// If the slow picks are faster, we reduce the flicker by
// only setting the items once.
// Fast Picks: if `mergeDelay` is configured, in order to reduce
// amount of flicker, we race against the slow picks over some delay
// and then set the fast picks.
// If the slow picks are faster, we reduce the flicker by only
// setting the items once.

(async () => {
await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY);
if (picksToken.isCancellationRequested) {
return;
if (typeof fastAndSlowPicks.mergeDelay === 'number') {
await timeout(fastAndSlowPicks.mergeDelay);
if (picksToken.isCancellationRequested) {
return;
}
}

if (!slowPicksApplied) {
fastPicksApplied = applyPicks(providedPicks.picks, true /* skip over empty to reduce flicker */);
fastPicksApplied = applyPicks(fastAndSlowPicks.picks, true /* skip over empty to reduce flicker */);
}
})(),

// Slow Picks: we await the slow picks and then set them at
// once together with the fast picks, but only if we actually
// have additional results.

(async () => {
picker.busy = true;
try {
const awaitedAdditionalPicks = await providedPicks.additionalPicks;
const awaitedAdditionalPicks = await fastAndSlowPicks.additionalPicks;
if (picksToken.isCancellationRequested) {
return;
}

let picks: readonly Pick<T>[];
let activePick: Pick<T> | undefined = undefined;
if (isPicksWithActive(providedPicks.picks)) {
picks = providedPicks.picks.items;
activePick = providedPicks.picks.active;
if (isPicksWithActive(fastAndSlowPicks.picks)) {
picks = fastAndSlowPicks.picks.items;
activePick = fastAndSlowPicks.picks.active;
} else {
picks = providedPicks.picks;
picks = fastAndSlowPicks.picks;
}

let additionalPicks: readonly Pick<T>[];
Expand Down Expand Up @@ -243,6 +259,16 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
}
})()
]);
};

// No Picks
if (providedPicks === null) {
// Ignore
}

// Fast and Slow Picks
else if (isFastAndSlowPicks(providedPicks)) {
await applyFastAndSlowPicks(providedPicks);
}

// Fast Picks
Expand All @@ -259,7 +285,11 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
return;
}

applyPicks(awaitedPicks);
if (isFastAndSlowPicks(awaitedPicks)) {
await applyFastAndSlowPicks(awaitedPicks);
} else {
applyPicks(awaitedPicks);
}
} finally {
if (!picksToken.isCancellationRequested) {
picker.busy = false;
Expand Down Expand Up @@ -343,5 +373,5 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
* @returns the picks either directly, as promise or combined fast and slow results.
* Pickers can return `null` to signal that no change in picks is needed.
*/
protected abstract _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): Picks<T> | Promise<Picks<T>> | FastAndSlowPicks<T> | null;
protected abstract _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): Picks<T> | Promise<Picks<T> | FastAndSlowPicks<T>> | FastAndSlowPicks<T> | null;
}
6 changes: 3 additions & 3 deletions src/vs/platform/quickinput/browser/quickAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
private readonly lastAcceptedPickerValues = new Map<IQuickAccessProviderDescriptor, string>();

private visibleQuickAccess: {
picker: IQuickPick<IQuickPickItem>;
descriptor: IQuickAccessProviderDescriptor | undefined;
value: string;
readonly picker: IQuickPick<IQuickPickItem>;
readonly descriptor: IQuickAccessProviderDescriptor | undefined;
readonly value: string;
} | undefined = undefined;

constructor(
Expand Down
17 changes: 9 additions & 8 deletions src/vs/platform/quickinput/common/quickAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,40 @@ import { Registry } from 'vs/platform/registry/common/platform';
* quick access.
*/
export interface IQuickAccessProviderRunOptions {
from?: string;
readonly from?: string;
}

/**
* The specific options for the AnythingQuickAccessProvider. Put here to share between layers.
*/
export interface AnythingQuickAccessProviderRunOptions extends IQuickAccessProviderRunOptions {
includeHelp?: boolean;
readonly includeHelp?: boolean;
}

export interface IQuickAccessOptions {

/**
* Allows to enable quick navigate support in quick input.
*/
quickNavigateConfiguration?: IQuickNavigateConfiguration;
readonly quickNavigateConfiguration?: IQuickNavigateConfiguration;

/**
* Allows to configure a different item activation strategy.
* By default the first item in the list will get activated.
*/
itemActivation?: ItemActivation;
readonly itemActivation?: ItemActivation;

/**
* Whether to take the input value as is and not restore it
* from any existing value if quick access is visible.
*/
preserveValue?: boolean;
readonly preserveValue?: boolean;

/**
* Provider specific options for this particular showing of the
* quick access.
*/
providerOptions?: IQuickAccessProviderRunOptions;
readonly providerOptions?: IQuickAccessProviderRunOptions;
}

export interface IQuickAccessController {
Expand Down Expand Up @@ -114,12 +114,12 @@ export interface IQuickAccessProviderHelp {
* The prefix to show for the help entry. If not provided,
* the prefix used for registration will be taken.
*/
prefix?: string;
readonly prefix?: string;

/**
* A description text to help understand the intent of the provider.
*/
description: string;
readonly description: string;

/**
* The command to bring up this quick access provider.
Expand Down Expand Up @@ -181,6 +181,7 @@ export interface IQuickAccessRegistry {
}

export class QuickAccessRegistry implements IQuickAccessRegistry {

private providers: IQuickAccessProviderDescriptor[] = [];
private defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined;

Expand Down
Loading

0 comments on commit 3a69e15

Please sign in to comment.