add cancellation support for showInput and showQuickPick, #9377
This commit is contained in:
parent
b0c702dd2f
commit
ff061902a7
|
@ -6,7 +6,7 @@
|
|||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {workspace, window, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position} from 'vscode';
|
||||
import {workspace, window, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource} from 'vscode';
|
||||
import {join} from 'path';
|
||||
import {cleanUp, pathEquals} from './utils';
|
||||
|
||||
|
@ -148,8 +148,45 @@ suite('window namespace tests', () => {
|
|||
});
|
||||
|
||||
test('#7013 - input without options', function () {
|
||||
|
||||
let p = window.showInputBox();
|
||||
const source = new CancellationTokenSource();
|
||||
let p = window.showInputBox(undefined, source.token);
|
||||
assert.ok(typeof p === 'object');
|
||||
source.dispose();
|
||||
});
|
||||
|
||||
test('showInputBox - undefined on cancel', function () {
|
||||
const source = new CancellationTokenSource();
|
||||
const p = window.showInputBox(undefined, source.token);
|
||||
source.cancel();
|
||||
return p.then(value => {
|
||||
assert.equal(value, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('showInputBox - cancel early', function () {
|
||||
const source = new CancellationTokenSource();
|
||||
source.cancel();
|
||||
const p = window.showInputBox(undefined, source.token);
|
||||
return p.then(value => {
|
||||
assert.equal(value, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('showQuickPick, undefined on cancel', function () {
|
||||
const source = new CancellationTokenSource();
|
||||
const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token);
|
||||
source.cancel();
|
||||
return p.then(value => {
|
||||
assert.equal(value, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('showQuickPick, cancel early', function () {
|
||||
const source = new CancellationTokenSource();
|
||||
source.cancel();
|
||||
const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token);
|
||||
return p.then(value => {
|
||||
assert.equal(value, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,8 +40,15 @@ export function asWinJsPromise<T>(callback: (token: CancellationToken) => T | Th
|
|||
/**
|
||||
* Hook a cancellation token to a WinJS Promise
|
||||
*/
|
||||
export function wireCancellationToken<T>(token: CancellationToken, promise: TPromise<T>): Thenable<T> {
|
||||
export function wireCancellationToken<T>(token: CancellationToken, promise: TPromise<T>, resolveAsUndefinedWhenCancelled?: boolean): Thenable<T> {
|
||||
token.onCancellationRequested(() => promise.cancel());
|
||||
if (resolveAsUndefinedWhenCancelled) {
|
||||
return promise.then(undefined, err => {
|
||||
if (!errors.isPromiseCanceledError(err)) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,11 @@ export interface CancellationToken {
|
|||
onCancellationRequested: Event<any>;
|
||||
}
|
||||
|
||||
const shortcutEvent: Event<any> = Object.freeze(function(callback, context?) {
|
||||
let handle = setTimeout(callback.bind(context), 0);
|
||||
return { dispose() { clearTimeout(handle); } };
|
||||
});
|
||||
|
||||
export namespace CancellationToken {
|
||||
|
||||
export const None: CancellationToken = Object.freeze({
|
||||
|
@ -21,15 +26,10 @@ export namespace CancellationToken {
|
|||
|
||||
export const Cancelled: CancellationToken = Object.freeze({
|
||||
isCancellationRequested: true,
|
||||
onCancellationRequested: Event.None
|
||||
onCancellationRequested: shortcutEvent
|
||||
});
|
||||
}
|
||||
|
||||
const shortcutEvent: Event<any> = Object.freeze(function(callback, context?) {
|
||||
let handle = setTimeout(callback.bind(context), 0);
|
||||
return { dispose() { clearTimeout(handle); } };
|
||||
});
|
||||
|
||||
class MutableToken implements CancellationToken {
|
||||
|
||||
private _isCancelled: boolean = false;
|
||||
|
|
|
@ -509,11 +509,11 @@ export class TestQuickOpenService implements QuickOpenService.IQuickOpenService
|
|||
this.callback = callback;
|
||||
}
|
||||
|
||||
pick(arg: any, placeHolder?: string, autoFocusFirst?: boolean): Promise {
|
||||
pick(arg: any, options?: any, token?: any): Promise {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
input(options?: any): Promise {
|
||||
input(options?: any, token?: any): Promise {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
|
|
9
src/vs/vscode.d.ts
vendored
9
src/vs/vscode.d.ts
vendored
|
@ -3324,18 +3324,20 @@ declare namespace vscode {
|
|||
*
|
||||
* @param items An array of strings, or a promise that resolves to an array of strings.
|
||||
* @param options Configures the behavior of the selection list.
|
||||
* @param token A token that can be used to signal cancellation.
|
||||
* @return A promise that resolves to the selection or undefined.
|
||||
*/
|
||||
export function showQuickPick(items: string[] | Thenable<string[]>, options?: QuickPickOptions): Thenable<string>;
|
||||
export function showQuickPick(items: string[] | Thenable<string[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<string>;
|
||||
|
||||
/**
|
||||
* Shows a selection list.
|
||||
*
|
||||
* @param items An array of items, or a promise that resolves to an array of items.
|
||||
* @param options Configures the behavior of the selection list.
|
||||
* @param token A token that can be used to signal cancellation.
|
||||
* @return A promise that resolves to the selected item or undefined.
|
||||
*/
|
||||
export function showQuickPick<T extends QuickPickItem>(items: T[] | Thenable<T[]>, options?: QuickPickOptions): Thenable<T>;
|
||||
export function showQuickPick<T extends QuickPickItem>(items: T[] | Thenable<T[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<T>;
|
||||
|
||||
/**
|
||||
* Opens an input box to ask the user for input.
|
||||
|
@ -3345,9 +3347,10 @@ declare namespace vscode {
|
|||
* anything but dismissed the input box with OK.
|
||||
*
|
||||
* @param options Configures the behavior of the input box.
|
||||
* @param token A token that can be used to signal cancellation.
|
||||
* @return A promise that resolves to a string the user provided or to `undefined` in case of dismissal.
|
||||
*/
|
||||
export function showInputBox(options?: InputBoxOptions): Thenable<string>;
|
||||
export function showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable<string>;
|
||||
|
||||
/**
|
||||
* Create a new [output channel](#OutputChannel) with the given name.
|
||||
|
|
|
@ -236,11 +236,11 @@ export class ExtHostAPIImplementation {
|
|||
showErrorMessage: (message, ...items) => {
|
||||
return extHostMessageService.showMessage(Severity.Error, message, items);
|
||||
},
|
||||
showQuickPick: (items: any, options: vscode.QuickPickOptions) => {
|
||||
return extHostQuickOpen.show(items, options);
|
||||
showQuickPick: (items: any, options: vscode.QuickPickOptions, token?: vscode.CancellationToken) => {
|
||||
return extHostQuickOpen.showQuickPick(items, options, token);
|
||||
},
|
||||
showInputBox(options?: vscode.InputBoxOptions) {
|
||||
return extHostQuickOpen.input(options);
|
||||
showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken) {
|
||||
return extHostQuickOpen.showInput(options, token);
|
||||
},
|
||||
|
||||
createStatusBarItem(position?: vscode.StatusBarAlignment, priority?: number): vscode.StatusBarItem {
|
||||
|
|
|
@ -153,7 +153,7 @@ export abstract class MainThreadQuickOpenShape {
|
|||
$show(options: IPickOptions): Thenable<number> { throw ni(); }
|
||||
$setItems(items: MyQuickPickItems[]): Thenable<any> { throw ni(); }
|
||||
$setError(error: Error): Thenable<any> { throw ni(); }
|
||||
$input(options: vscode.InputBoxOptions, validateInput: boolean): Thenable<string> { throw ni(); }
|
||||
$input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string> { throw ni(); }
|
||||
}
|
||||
|
||||
export abstract class MainThreadStatusBarShape {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
'use strict';
|
||||
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {wireCancellationToken} from 'vs/base/common/async';
|
||||
import {CancellationToken} from 'vs/base/common/cancellation';
|
||||
import {IThreadService} from 'vs/workbench/services/thread/common/threadService';
|
||||
import {QuickPickOptions, QuickPickItem, InputBoxOptions} from 'vscode';
|
||||
import {MainContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems} from './extHost.protocol';
|
||||
|
@ -22,26 +24,21 @@ export class ExtHostQuickOpen extends ExtHostQuickOpenShape {
|
|||
this._proxy = threadService.get(MainContext.MainThreadQuickOpen);
|
||||
}
|
||||
|
||||
show(itemsOrItemsPromise: Item[] | Thenable<Item[]>, options?: QuickPickOptions): Thenable<Item> {
|
||||
showQuickPick(itemsOrItemsPromise: Item[] | Thenable<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Thenable<Item> {
|
||||
|
||||
// clear state from last invocation
|
||||
this._onDidSelectItem = undefined;
|
||||
|
||||
let itemsPromise: Thenable<Item[]>;
|
||||
if (!Array.isArray(itemsOrItemsPromise)) {
|
||||
itemsPromise = itemsOrItemsPromise;
|
||||
} else {
|
||||
itemsPromise = TPromise.as(itemsOrItemsPromise);
|
||||
}
|
||||
const itemsPromise = <TPromise<Item[]>> TPromise.as(itemsOrItemsPromise);
|
||||
|
||||
let quickPickWidget = this._proxy.$show({
|
||||
const quickPickWidget = this._proxy.$show({
|
||||
autoFocus: { autoFocusFirstEntry: true },
|
||||
placeHolder: options && options.placeHolder,
|
||||
matchOnDescription: options && options.matchOnDescription,
|
||||
matchOnDetail: options && options.matchOnDetail
|
||||
});
|
||||
|
||||
return itemsPromise.then(items => {
|
||||
const promise = itemsPromise.then(items => {
|
||||
|
||||
let pickItems: MyQuickPickItems[] = [];
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
|
@ -86,6 +83,8 @@ export class ExtHostQuickOpen extends ExtHostQuickOpenShape {
|
|||
|
||||
return TPromise.wrapError(err);
|
||||
});
|
||||
|
||||
return wireCancellationToken(token, promise, true);
|
||||
}
|
||||
|
||||
$onItemSelected(handle: number): void {
|
||||
|
@ -96,9 +95,13 @@ export class ExtHostQuickOpen extends ExtHostQuickOpenShape {
|
|||
|
||||
// ---- input
|
||||
|
||||
input(options?: InputBoxOptions): Thenable<string> {
|
||||
showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Thenable<string> {
|
||||
|
||||
// global validate fn used in callback below
|
||||
this._validateInput = options && options.validateInput;
|
||||
return this._proxy.$input(options, options && typeof options.validateInput === 'function');
|
||||
|
||||
const promise = this._proxy.$input(options, typeof this._validateInput === 'function');
|
||||
return wireCancellationToken(token, promise, true);
|
||||
}
|
||||
|
||||
$validateInput(input: string): TPromise<string> {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
'use strict';
|
||||
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {asWinJsPromise} from 'vs/base/common/async';
|
||||
import {IThreadService} from 'vs/workbench/services/thread/common/threadService';
|
||||
import {IQuickOpenService, IPickOptions, IInputOptions} from 'vs/workbench/services/quickopen/common/quickOpenService';
|
||||
import {InputBoxOptions} from 'vscode';
|
||||
|
@ -46,7 +47,7 @@ export class MainThreadQuickOpen extends MainThreadQuickOpenShape {
|
|||
};
|
||||
});
|
||||
|
||||
return this._quickOpenService.pick(this._contents, options).then(item => {
|
||||
return asWinJsPromise(token => this._quickOpenService.pick(this._contents, options, token)).then(item => {
|
||||
if (item) {
|
||||
return item.handle;
|
||||
}
|
||||
|
@ -73,7 +74,7 @@ export class MainThreadQuickOpen extends MainThreadQuickOpenShape {
|
|||
|
||||
// ---- input
|
||||
|
||||
$input(options: InputBoxOptions, validateInput: boolean): Thenable<string> {
|
||||
$input(options: InputBoxOptions, validateInput: boolean): TPromise<string> {
|
||||
|
||||
const inputOptions: IInputOptions = Object.create(null);
|
||||
|
||||
|
@ -90,6 +91,6 @@ export class MainThreadQuickOpen extends MainThreadQuickOpenShape {
|
|||
};
|
||||
}
|
||||
|
||||
return this._quickOpenService.input(inputOptions);
|
||||
return asWinJsPromise(token => this._quickOpenService.input(inputOptions, token));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,10 @@ import filters = require('vs/base/common/filters');
|
|||
import URI from 'vs/base/common/uri';
|
||||
import uuid = require('vs/base/common/uuid');
|
||||
import types = require('vs/base/common/types');
|
||||
import {CancellationToken} from 'vs/base/common/cancellation';
|
||||
import {Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel} from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import {QuickOpenEntryItem, QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup} from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import {QuickOpenWidget} from 'vs/base/parts/quickopen/browser/quickOpenWidget';
|
||||
import {QuickOpenWidget, HideReason} from 'vs/base/parts/quickopen/browser/quickOpenWidget';
|
||||
import {ContributableActionProvider} from 'vs/workbench/browser/actionBarRegistry';
|
||||
import {ITree, IElementCallback} from 'vs/base/parts/tree/browser/tree';
|
||||
import labels = require('vs/base/common/labels');
|
||||
|
@ -123,7 +124,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
|
|||
}
|
||||
}
|
||||
|
||||
public input(options: IInputOptions = {}): TPromise<string> {
|
||||
public input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): TPromise<string> {
|
||||
const defaultMessage = options.prompt
|
||||
? nls.localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", options.prompt)
|
||||
: nls.localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
|
||||
|
@ -167,7 +168,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
|
|||
});
|
||||
}
|
||||
}
|
||||
}).then(resolve, reject);
|
||||
}, token).then(resolve, reject);
|
||||
};
|
||||
|
||||
return new TPromise(init).then(item => {
|
||||
|
@ -179,11 +180,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
|
|||
});
|
||||
}
|
||||
|
||||
public pick(picks: TPromise<string[]>, options?: IPickOptions): TPromise<string>;
|
||||
public pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions): TPromise<string>;
|
||||
public pick(picks: string[], options?: IPickOptions): TPromise<string>;
|
||||
public pick<T extends IPickOpenEntry>(picks: T[], options?: IPickOptions): TPromise<T>;
|
||||
public pick(arg1: string[] | TPromise<string[]> | IPickOpenEntry[] | TPromise<IPickOpenEntry[]>, options?: IPickOptions): TPromise<string | IPickOpenEntry> {
|
||||
public pick(picks: TPromise<string[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string>;
|
||||
public pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string>;
|
||||
public pick(picks: string[], options?: IPickOptions, token?: CancellationToken): TPromise<string>;
|
||||
public pick<T extends IPickOpenEntry>(picks: T[], options?: IPickOptions, token?: CancellationToken): TPromise<T>;
|
||||
public pick(arg1: string[] | TPromise<string[]> | IPickOpenEntry[] | TPromise<IPickOpenEntry[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string | IPickOpenEntry> {
|
||||
if (!options) {
|
||||
options = Object.create(null);
|
||||
}
|
||||
|
@ -215,11 +216,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
|
|||
return item && isAboutStrings ? item.label : item;
|
||||
}
|
||||
|
||||
this.doPick(entryPromise, options).then(item => resolve(onItem(item)), err => reject(err), item => progress(onItem(item)));
|
||||
this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err), item => progress(onItem(item)));
|
||||
});
|
||||
}
|
||||
|
||||
private doPick(picksPromise: TPromise<IPickOpenEntry[]>, options: IInternalPickOptions): TPromise<IPickOpenEntry> {
|
||||
private doPick(picksPromise: TPromise<IPickOpenEntry[]>, options: IInternalPickOptions, token: CancellationToken = CancellationToken.None): TPromise<IPickOpenEntry> {
|
||||
let autoFocus = options.autoFocus;
|
||||
|
||||
// Use a generated token to avoid race conditions from long running promises
|
||||
|
@ -276,6 +277,10 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
|
|||
}
|
||||
|
||||
return new TPromise<IPickOpenEntry | string>((complete, error, progress) => {
|
||||
|
||||
// hide widget when being cancelled
|
||||
token.onCancellationRequested(e => this.pickOpenWidget.hide(HideReason.CANCELED));
|
||||
|
||||
let picksPromiseDone = false;
|
||||
|
||||
// Resolve picks
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import Event from 'vs/base/common/event';
|
||||
import {CancellationToken} from 'vs/base/common/cancellation';
|
||||
import {IQuickNavigateConfiguration, IAutoFocus, IEntryRunContext} from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import {createDecorator} from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
|
@ -108,10 +109,10 @@ export interface IQuickOpenService {
|
|||
* Passing in a promise will allow you to resolve the elements in the background while quick open will show a
|
||||
* progress bar spinning.
|
||||
*/
|
||||
pick(picks: TPromise<string[]>, options?: IPickOptions): TPromise<string>;
|
||||
pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions): TPromise<T>;
|
||||
pick(picks: string[], options?: IPickOptions): TPromise<string>;
|
||||
pick<T extends IPickOpenEntry>(picks: T[], options?: IPickOptions): TPromise<T>;
|
||||
pick(picks: TPromise<string[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string>;
|
||||
pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions, token?: CancellationToken): TPromise<T>;
|
||||
pick(picks: string[], options?: IPickOptions, token?: CancellationToken): TPromise<string>;
|
||||
pick<T extends IPickOpenEntry>(picks: T[], options?: IPickOptions, token?: CancellationToken): TPromise<T>;
|
||||
|
||||
/**
|
||||
* Should not be used by clients. Will cause any opened quick open widget to navigate in the result set.
|
||||
|
@ -121,7 +122,7 @@ export interface IQuickOpenService {
|
|||
/**
|
||||
* Opens the quick open box for user input and returns a promise with the user typed value if any.
|
||||
*/
|
||||
input(options?: IInputOptions): TPromise<string>;
|
||||
input(options?: IInputOptions, token?: CancellationToken): TPromise<string>;
|
||||
|
||||
/**
|
||||
* Allows to register on the event that quick open is showing
|
||||
|
|
Loading…
Reference in a new issue