diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f0db65dcd25..1c0ef295c1f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -2762,4 +2762,11 @@ declare module 'vscode' { //#endregion + //#region quickPickItemKind: https://github.com/microsoft/vscode/issues/74967 + + export interface QuickPickItem { + kind?: string | { label: string; }; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index 6baffac194d..9a47764e962 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IPickOptions, IInputOptions, IQuickInputService, IQuickInput, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, TransferQuickPickItems, MainContext, IExtHostContext, TransferQuickInput, TransferQuickInputButton, IInputBoxOptions } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, TransferQuickPickItem, MainContext, IExtHostContext, TransferQuickInput, TransferQuickInputButton, IInputBoxOptions, TransferQuickPickItemOrSeparator } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; interface QuickInputSession { input: IQuickInput; - handlesToItems: Map; + handlesToItems: Map; } function reviveIconPathUris(iconPath: { dark: URI; light?: URI | undefined; }) { @@ -27,7 +27,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { private readonly _proxy: ExtHostQuickOpenShape; private readonly _quickInputService: IQuickInputService; private readonly _items: Record = {}; @@ -42,8 +42,8 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { public dispose(): void { } - $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { - const contents = new Promise((resolve, reject) => { + $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { + const contents = new Promise((resolve, reject) => { this._items[instance] = { resolve, reject }; }); @@ -51,7 +51,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { ...options, onDidFocus: el => { if (el) { - this._proxy.$onItemSelected((el).handle); + this._proxy.$onItemSelected((el).handle); } } }; @@ -73,7 +73,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } } - $setItems(instance: number, items: TransferQuickPickItems[]): Promise { + $setItems(instance: number, items: TransferQuickPickItemOrSeparator[]): Promise { if (this._items[instance]) { this._items[instance].resolve(items); delete this._items[instance]; @@ -140,13 +140,13 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { // Add extra events specific for quickpick const quickpick = input as IQuickPick; quickpick.onDidChangeActive(items => { - this._proxy.$onDidChangeActive(sessionId, items.map(item => (item as TransferQuickPickItems).handle)); + this._proxy.$onDidChangeActive(sessionId, items.map(item => (item as TransferQuickPickItem).handle)); }); quickpick.onDidChangeSelection(items => { - this._proxy.$onDidChangeSelection(sessionId, items.map(item => (item as TransferQuickPickItems).handle)); + this._proxy.$onDidChangeSelection(sessionId, items.map(item => (item as TransferQuickPickItem).handle)); }); quickpick.onDidTriggerItemButton((e) => { - this._proxy.$onDidTriggerItemButton(sessionId, (e.item as TransferQuickPickItems).handle, (e.button as TransferQuickInputButton).handle); + this._proxy.$onDidTriggerItemButton(sessionId, (e.item as TransferQuickPickItem).handle, (e.button as TransferQuickInputButton).handle); }); } @@ -169,7 +169,11 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } } else if (param === 'items') { handlesToItems.clear(); - params[param].forEach((item: TransferQuickPickItems) => { + params[param].forEach((item: TransferQuickPickItemOrSeparator) => { + if (item.type === 'separator') { + return; + } + if (item.buttons) { item.buttons = item.buttons.map((button: TransferQuickInputButton) => { if (button.iconPath) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 12c7f9a5bd3..2144f6df3e6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -510,7 +510,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $sendProcessExit(terminalId: number, exitCode: number | undefined): void; } -export interface TransferQuickPickItems extends quickInput.IQuickPickItem { +export type TransferQuickPickItemOrSeparator = TransferQuickPickItem | quickInput.IQuickPickSeparator; +export interface TransferQuickPickItem extends quickInput.IQuickPickItem { handle: number; buttons?: TransferQuickInputButton[]; } @@ -548,7 +549,7 @@ export interface TransferQuickPick extends BaseTransferQuickInput { buttons?: TransferQuickInputButton[]; - items?: TransferQuickPickItems[]; + items?: TransferQuickPickItemOrSeparator[]; activeItems?: number[]; @@ -593,8 +594,8 @@ export interface IInputBoxOptions { } export interface MainThreadQuickOpenShape extends IDisposable { - $show(instance: number, options: quickInput.IPickOptions, token: CancellationToken): Promise; - $setItems(instance: number, items: TransferQuickPickItems[]): Promise; + $show(instance: number, options: quickInput.IPickOptions, token: CancellationToken): Promise; + $setItems(instance: number, items: TransferQuickPickItemOrSeparator[]): Promise; $setError(instance: number, error: Error): Promise; $input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise; $createOrUpdate(params: TransferQuickInput): Promise; diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 96374d8e4be..4345d936c4a 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -10,7 +10,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickItemButtonEvent, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; -import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickPickItems, TransferQuickInput, TransferQuickInputButton } from './extHost.protocol'; +import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickInput, TransferQuickInputButton, TransferQuickPickItemOrSeparator } from './extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/common/extHostTypes'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -59,7 +59,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, enableProposedApi: boolean, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Promise; showQuickPick(itemsOrItemsPromise: string[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: Item[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise { + async showQuickPick(itemsOrItemsPromise: Item[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise { // clear state from last invocation this._onDidSelectItem = undefined; @@ -80,70 +80,81 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx const widgetClosedMarker = {}; const widgetClosedPromise = quickPickWidget.then(() => widgetClosedMarker); - return Promise.race([widgetClosedPromise, itemsPromise]).then(result => { - if (result === widgetClosedMarker) { - return undefined; + const result = await Promise.race([widgetClosedPromise, itemsPromise]); + if (result === widgetClosedMarker) { + return undefined; + } + + const items = await itemsPromise; + + const pickItems: TransferQuickPickItemOrSeparator[] = []; + let lastKind: string | { label: string; } | undefined = undefined; + for (let handle = 0; handle < items.length; handle++) { + const item = items[handle]; + let label: string; + let description: string | undefined; + let detail: string | undefined; + let picked: boolean | undefined; + let alwaysShow: boolean | undefined; + + if (typeof item === 'string') { + // 'string' items have a kind of undefined so + // if the previous item had a kind, that is considered a + // change and would cause the addition of a separator. + if (lastKind) { + pickItems.push({ type: 'separator' }); + lastKind = undefined; + } + label = item; + } else { + if (lastKind !== item.kind) { + const label: string | undefined = typeof item.kind === 'string' ? item.kind : item.kind?.label; + pickItems.push({ type: 'separator', label }); + lastKind = item.kind; + } + label = item.label; + description = item.description; + detail = item.detail; + picked = item.picked; + alwaysShow = item.alwaysShow; } - - return itemsPromise.then(items => { - - const pickItems: TransferQuickPickItems[] = []; - for (let handle = 0; handle < items.length; handle++) { - - const item = items[handle]; - let label: string; - let description: string | undefined; - let detail: string | undefined; - let picked: boolean | undefined; - let alwaysShow: boolean | undefined; - - if (typeof item === 'string') { - label = item; - } else { - label = item.label; - description = item.description; - detail = item.detail; - picked = item.picked; - alwaysShow = item.alwaysShow; - } - pickItems.push({ - label, - description, - handle, - detail, - picked, - alwaysShow - }); - } - - // handle selection changes - if (options && typeof options.onDidSelectItem === 'function') { - this._onDidSelectItem = (handle) => { - options.onDidSelectItem!(items[handle]); - }; - } - - // show items - proxy.$setItems(instance, pickItems); - - return quickPickWidget.then(handle => { - if (typeof handle === 'number') { - return items[handle]; - } else if (Array.isArray(handle)) { - return handle.map(h => items[h]); - } - return undefined; - }); + pickItems.push({ + label, + description, + handle, + detail, + picked, + alwaysShow }); - }).then(undefined, err => { + } + + // handle selection changes + if (options && typeof options.onDidSelectItem === 'function') { + this._onDidSelectItem = (handle) => { + options.onDidSelectItem!(items[handle]); + }; + } + + // show items + proxy.$setItems(instance, pickItems); + + try { + return quickPickWidget.then(handle => { + if (typeof handle === 'number') { + return items[handle]; + } else if (Array.isArray(handle)) { + return handle.map(h => items[h]); + } + return undefined; + }); + } catch (err) { if (isPromiseCanceledError(err)) { return undefined; } proxy.$setError(instance, err); - - return Promise.reject(err); - }); + throw err; + } } $onItemSelected(handle: number): void { @@ -553,8 +564,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._handlesToItems.set(i, item); this._itemsToHandles.set(item, i); }); - this.update({ - items: items.map((item, i) => ({ + const itemsItems: Array = []; + let lastKind: string | { label: string; } | undefined = undefined; + items.forEach((item, i) => { + + if (lastKind !== item.kind) { + const label: string | undefined = typeof item.kind === 'string' ? item.kind : item.kind?.label; + itemsItems.push({ type: 'separator', label }); + lastKind = item.kind; + } + itemsItems.push({ label: item.label, description: item.description, handle: i, @@ -567,8 +586,12 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx tooltip: button.tooltip, handle: i }; - }), - })) + }) + }); + }); + + this.update({ + items: itemsItems }); }