Multi-step input API (#49340)

This commit is contained in:
Christof Marti
2018-05-14 12:51:12 +02:00
parent 1055627274
commit e142962e1b
10 changed files with 254 additions and 32 deletions

View File

@@ -6,11 +6,17 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { asWinJsPromise } from 'vs/base/common/async';
import { IPickOptions, IInputOptions, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { InputBoxOptions } from 'vscode';
import { IPickOptions, IInputOptions, IQuickInputService, IQuickInput } from 'vs/platform/quickinput/common/quickInput';
import { InputBoxOptions, CancellationToken } from 'vscode';
import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
interface MultiStepSession {
handle: number;
input: IQuickInput;
token: CancellationToken;
}
@extHostNamedCustomer(MainContext.MainThreadQuickOpen)
export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
@@ -20,6 +26,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
private _doSetError: (error: Error) => any;
private _contents: TPromise<MyQuickPickItems[]>;
private _token: number = 0;
private _multiStep: MultiStepSession;
constructor(
extHostContext: IExtHostContext,
@@ -32,7 +39,13 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
public dispose(): void {
}
$show(options: IPickOptions): TPromise<number | number[]> {
$show(multiStepHandle: number | undefined, options: IPickOptions): TPromise<number | number[]> {
const multiStep = typeof multiStepHandle === 'number';
if (multiStep && !(this._multiStep && multiStepHandle === this._multiStep.handle && !this._multiStep.token.isCancellationRequested)) {
return TPromise.as(undefined);
}
const input: IQuickInput = multiStep ? this._multiStep.input : this._quickInputService;
const myToken = ++this._token;
@@ -51,7 +64,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
});
if (options.canPickMany) {
return asWinJsPromise(token => this._quickInputService.pick(this._contents, options as { canPickMany: true }, token)).then(items => {
return asWinJsPromise(token => input.pick(this._contents, options as { canPickMany: true }, token)).then(items => {
if (items) {
return items.map(item => item.handle);
}
@@ -62,7 +75,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
}
});
} else {
return asWinJsPromise(token => this._quickInputService.pick(this._contents, options, token)).then(item => {
return asWinJsPromise(token => input.pick(this._contents, options, token)).then(item => {
if (item) {
return item.handle;
}
@@ -91,7 +104,13 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
// ---- input
$input(options: InputBoxOptions, validateInput: boolean): TPromise<string> {
$input(multiStepHandle: number | undefined, options: InputBoxOptions, validateInput: boolean): TPromise<string> {
const multiStep = typeof multiStepHandle === 'number';
if (multiStep && !(this._multiStep && multiStepHandle === this._multiStep.handle && !this._multiStep.token.isCancellationRequested)) {
return TPromise.as(undefined);
}
const input: IQuickInput = multiStep ? this._multiStep.input : this._quickInputService;
const inputOptions: IInputOptions = Object.create(null);
@@ -110,6 +129,27 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
};
}
return asWinJsPromise(token => this._quickInputService.input(inputOptions, token));
return asWinJsPromise(token => input.input(inputOptions, token));
}
// ---- Multi-step input
$multiStep(handle: number): TPromise<never> {
let outerReject: (err: any) => void;
let innerResolve: (value: void) => void;
const promise = new TPromise<never>((_, rej) => outerReject = rej, () => innerResolve(undefined));
this._quickInputService.multiStepInput((input, token) => {
this._multiStep = { handle, input, token };
const promise = new TPromise<void>(res => innerResolve = res);
token.onCancellationRequested(() => innerResolve(undefined));
return promise;
})
.then(() => promise.cancel(), err => outerReject(err))
.then(() => {
if (this._multiStep && this._multiStep.handle === handle) {
this._multiStep = null;
}
});
return promise;
}
}

View File

@@ -386,13 +386,16 @@ export function createApiFactory(
return extHostMessageService.showMessage(extension, Severity.Error, message, first, rest);
},
showQuickPick(items: any, options: vscode.QuickPickOptions, token?: vscode.CancellationToken): any {
return extHostQuickOpen.showQuickPick(items, options, token);
return extHostQuickOpen.showQuickPick(undefined, items, options, token);
},
showWorkspaceFolderPick(options: vscode.WorkspaceFolderPickOptions) {
return extHostQuickOpen.showWorkspaceFolderPick(options);
},
showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken) {
return extHostQuickOpen.showInput(options, token);
return extHostQuickOpen.showInput(undefined, options, token);
},
multiStepInput<T>(handler: (input: vscode.QuickInput, token: vscode.CancellationToken) => Thenable<T>, token?: vscode.CancellationToken): Thenable<T> {
return extHostQuickOpen.multiStepInput(handler, token);
},
showOpenDialog(options) {
return extHostDialogs.showOpenDialog(options);

View File

@@ -332,10 +332,11 @@ export interface MyQuickPickItems extends IPickOpenEntry {
handle: number;
}
export interface MainThreadQuickOpenShape extends IDisposable {
$show(options: IPickOptions): TPromise<number | number[]>;
$show(multiStepHandle: number | undefined, options: IPickOptions): TPromise<number | number[]>;
$setItems(items: MyQuickPickItems[]): TPromise<any>;
$setError(error: Error): TPromise<any>;
$input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string>;
$input(multiStepHandle: number | undefined, options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string>;
$multiStep(handle: number): TPromise<never>;
}
export interface MainThreadStatusBarShape extends IDisposable {

View File

@@ -6,11 +6,12 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { wireCancellationToken, asWinJsPromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { QuickPickOptions, QuickPickItem, InputBoxOptions, WorkspaceFolderPickOptions, WorkspaceFolder } from 'vscode';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { QuickPickOptions, QuickPickItem, InputBoxOptions, WorkspaceFolderPickOptions, WorkspaceFolder, QuickInput } from 'vscode';
import { MainContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, IMainContext } from './extHost.protocol';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
import { isPromiseCanceledError } from 'vs/base/common/errors';
export type Item = string | QuickPickItem;
@@ -23,23 +24,25 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
private _onDidSelectItem: (handle: number) => void;
private _validateInput: (input: string) => string | Thenable<string>;
private _nextMultiStepHandle = 1;
constructor(mainContext: IMainContext, workspace: ExtHostWorkspace, commands: ExtHostCommands) {
this._proxy = mainContext.getProxy(MainContext.MainThreadQuickOpen);
this._workspace = workspace;
this._commands = commands;
}
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Thenable<QuickPickItem[] | undefined>;
showQuickPick(itemsOrItemsPromise: string[] | Thenable<string[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<string | undefined>;
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<QuickPickItem | undefined>;
showQuickPick(itemsOrItemsPromise: Item[] | Thenable<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Thenable<Item | Item[] | undefined> {
showQuickPick(multiStepHandle: number | undefined, itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Thenable<QuickPickItem[] | undefined>;
showQuickPick(multiStepHandle: number | undefined, itemsOrItemsPromise: string[] | Thenable<string[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<string | undefined>;
showQuickPick(multiStepHandle: number | undefined, itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<QuickPickItem | undefined>;
showQuickPick(multiStepHandle: number | undefined, itemsOrItemsPromise: Item[] | Thenable<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Thenable<Item | Item[] | undefined> {
// clear state from last invocation
this._onDidSelectItem = undefined;
const itemsPromise = <TPromise<Item[]>>TPromise.wrap(itemsOrItemsPromise);
const quickPickWidget = this._proxy.$show({
const quickPickWidget = this._proxy.$show(multiStepHandle, {
placeHolder: options && options.placeHolder,
matchOnDescription: options && options.matchOnDescription,
matchOnDetail: options && options.matchOnDetail,
@@ -115,12 +118,12 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
// ---- input
showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Thenable<string> {
showInput(multiStepHandle: number | undefined, options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Thenable<string> {
// global validate fn used in callback below
this._validateInput = options && options.validateInput;
const promise = this._proxy.$input(options, typeof this._validateInput === 'function');
const promise = this._proxy.$input(multiStepHandle, options, typeof this._validateInput === 'function');
return wireCancellationToken(token, promise, true);
}
@@ -142,4 +145,42 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
return this._workspace.getWorkspaceFolders().filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0];
});
}
// ---- Multi-step input
multiStepInput<T>(handler: (input: QuickInput, token: CancellationToken) => Thenable<T>, clientToken: CancellationToken = CancellationToken.None): Thenable<T> {
const handle = this._nextMultiStepHandle++;
const remotePromise = this._proxy.$multiStep(handle);
const cancellationSource = new CancellationTokenSource();
const handlerPromise = TPromise.wrap(handler({
showQuickPick: this.showQuickPick.bind(this, handle),
showInputBox: this.showInput.bind(this, handle)
}, cancellationSource.token));
clientToken.onCancellationRequested(() => {
remotePromise.cancel();
cancellationSource.cancel();
});
return TPromise.join<void, T>([
remotePromise.then(() => {
throw new Error('Unexpectedly fulfilled promise.');
}, err => {
if (!isPromiseCanceledError(err)) {
throw err;
}
cancellationSource.cancel();
}),
handlerPromise.then(result => {
remotePromise.cancel();
return result;
}, err => {
remotePromise.cancel();
throw err;
})
]).then(([_, result]) => result, ([remoteErr, handlerErr]) => {
throw handlerErr || remoteErr;
});
}
}