diff --git a/packages/core/src/browser/quick-open/quick-input-service.ts b/packages/core/src/browser/quick-open/quick-input-service.ts index ab71478c591e4..ac76cfb067bff 100644 --- a/packages/core/src/browser/quick-open/quick-input-service.ts +++ b/packages/core/src/browser/quick-open/quick-input-service.ts @@ -23,6 +23,7 @@ import { MessageType } from '../../common/message-service-protocol'; import { Emitter, Event } from '../../common/event'; import { QuickTitleBar } from './quick-title-bar'; import { QuickTitleButton } from '../../common/quick-open-model'; +import { CancellationToken } from '../../common/cancellation'; export interface QuickInputOptions { @@ -113,7 +114,7 @@ export class QuickInputService { @inject(QuickTitleBar) protected readonly quickTitleBar: QuickTitleBar; - open(options: QuickInputOptions): Promise { + open(options: QuickInputOptions, token: CancellationToken = CancellationToken.None): Promise { const result = new Deferred(); const prompt = this.createPrompt(options.prompt); let label = prompt; @@ -121,6 +122,14 @@ export class QuickInputService { const validateInput = options && options.validateInput; let initial: boolean = true; + const toDispose = token.onCancellationRequested(() => + this.quickOpenService.hide() + ); + const resolve = (value: string | undefined) => { + toDispose.dispose(); + result.resolve(value); + this.quickTitleBar.hide(); + }; this.quickOpenService.open({ onType: async (lookFor, acceptor) => { let error: string | undefined; @@ -140,9 +149,8 @@ export class QuickInputService { label, run: mode => { if (!error && mode === QuickOpenMode.OPEN) { - result.resolve(currentText); this.onDidAcceptEmitter.fire(undefined); - this.quickTitleBar.hide(); + resolve(currentText); return true; } return false; @@ -151,17 +159,17 @@ export class QuickInputService { currentText = lookFor; } }, { - prefix: options.value, - placeholder: options.placeHolder, - password: options.password, - ignoreFocusOut: options.ignoreFocusOut, - enabled: options.enabled, - valueSelection: options.valueSelection, - onClose: () => { - result.resolve(undefined); - this.quickTitleBar.hide(); - } - }); + prefix: options.value, + placeholder: options.placeHolder, + password: options.password, + ignoreFocusOut: options.ignoreFocusOut, + enabled: options.enabled, + valueSelection: options.valueSelection, + onClose: () => { + result.resolve(undefined); + this.quickTitleBar.hide(); + } + }); if (options && this.quickTitleBar.shouldShowTitleBar(options.title, options.step)) { this.quickTitleBar.attachTitleBar(this.quickOpenService.widgetNode, options.title, options.step, options.totalSteps, options.buttons); diff --git a/packages/plugin-ext/src/common/async-util.ts b/packages/plugin-ext/src/common/async-util.ts deleted file mode 100644 index 48e80583ad96d..0000000000000 --- a/packages/plugin-ext/src/common/async-util.ts +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************** - * Copyright (C) 2018 Red Hat, Inc. and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { CancellationToken } from '@theia/core/lib/common/cancellation'; -import { Deferred } from '@theia/core/lib/common/promise-util'; - -export function hookCancellationToken(token: CancellationToken, promise: Promise): PromiseLike { - return new Promise((resolve, reject) => { - const sub = token.onCancellationRequested(() => reject(new Error('This promise is cancelled'))); - promise.then(value => { - sub.dispose(); - resolve(value); - }).catch(err => { - sub.dispose(); - reject(err); - }); - }); -} - -export function anyPromise(promises: PromiseLike[]): Promise<{ key: number; value: PromiseLike; }> { - const result = new Deferred<{ key: number; value: PromiseLike; }>(); - if (promises.length === 0) { - result.resolve(); - } - - promises.forEach((val, key) => { - Promise.resolve(promises[key]).then(() => { - result.resolve({ key: key, value: promises[key] }); - }, err => { - result.resolve({ key: key, value: promises[key] }); - }); - }); - return result.promise; -} diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 7d5c500590acf..5f25f6944b8fd 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -472,10 +472,9 @@ export interface TransferQuickPick extends Transf } export interface QuickOpenMain { - $show(options: PickOptions): Promise; + $show(options: PickOptions, token: CancellationToken): Promise; $setItems(items: PickOpenItem[]): Promise; - $setError(error: Error): Promise; - $input(options: theia.InputBoxOptions, validateInput: boolean): Promise; + $input(options: theia.InputBoxOptions, validateInput: boolean, token: CancellationToken): Promise; $hide(): void; $showInputBox(inputBox: TransferInputBox, validateInput: boolean): void; $showCustomQuickPick(inputBox: TransferQuickPick): void; diff --git a/packages/plugin-ext/src/main/browser/quick-open-main.ts b/packages/plugin-ext/src/main/browser/quick-open-main.ts index 8e6170e72a192..e4f070cd21832 100644 --- a/packages/plugin-ext/src/main/browser/quick-open-main.ts +++ b/packages/plugin-ext/src/main/browser/quick-open-main.ts @@ -42,6 +42,7 @@ import { QuickPickService, QuickPickItem, QuickPickValue } from '@theia/core/lib import { QuickTitleBar } from '@theia/core/lib/browser/quick-open/quick-title-bar'; import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable'; import { QuickTitleButtonSide, QuickOpenGroupItem } from '@theia/core/lib/common/quick-open-model'; +import { CancellationToken } from '@theia/core/lib/common/cancellation'; export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel, Disposable { @@ -84,21 +85,29 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel, Disposa this.activeElement = undefined; } - $show(options: PickOptions): Promise { - this.activeElement = window.document.activeElement as HTMLElement; - this.delegate.open(this, { - fuzzyMatchDescription: options.matchOnDescription, - fuzzyMatchLabel: true, - fuzzyMatchDetail: options.matchOnDetail, - placeholder: options.placeHolder, - ignoreFocusOut: options.ignoreFocusLost, - onClose: () => { - this.cleanUp(); - } - }); - + $show(options: PickOptions, token: CancellationToken): Promise { return new Promise((resolve, reject) => { + if (token.isCancellationRequested) { + resolve(undefined); + return; + } this.doResolve = resolve; + this.activeElement = window.document.activeElement as HTMLElement; + const toDispose = token.onCancellationRequested(() => + this.delegate.hide() + ); + this.delegate.open(this, { + fuzzyMatchDescription: options.matchOnDescription, + fuzzyMatchLabel: true, + fuzzyMatchDetail: options.matchOnDetail, + placeholder: options.placeHolder, + ignoreFocusOut: options.ignoreFocusLost, + onClose: () => { + this.doResolve(undefined); + toDispose.dispose(); + this.cleanUp(); + } + }); }); } @@ -153,17 +162,12 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel, Disposa return convertedItems; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - $setError(error: Error): Promise { - throw new Error('Method not implemented.'); - } - - $input(options: InputBoxOptions, validateInput: boolean): Promise { + $input(options: InputBoxOptions, validateInput: boolean, token: CancellationToken): Promise { if (validateInput) { options.validateInput = val => this.proxy.$validateInput(val); } - return this.quickInput.open(options); + return this.quickInput.open(options, token); } protected convertQuickInputButton(quickInputButton: QuickInputButton, index: number, toDispose: DisposableCollection): QuickInputTitleButtonHandle { diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 298bfd8195b39..2eb553039d484 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -295,13 +295,7 @@ export function createAPIFactory( }, // eslint-disable-next-line @typescript-eslint/no-explicit-any showQuickPick(items: any, options: theia.QuickPickOptions, token?: theia.CancellationToken): any { - if (token) { - const coreEvent = Object.assign(token.onCancellationRequested, { maxListeners: 0 }); - const coreCancellationToken = { isCancellationRequested: token.isCancellationRequested, onCancellationRequested: coreEvent }; - return quickOpenExt.showQuickPick(items, options, coreCancellationToken); - } else { - return quickOpenExt.showQuickPick(items, options); - } + return quickOpenExt.showQuickPick(items, options, token); }, createQuickPick(): QuickPick { return quickOpenExt.createQuickPick(plugin); @@ -326,13 +320,7 @@ export function createAPIFactory( return statusBarMessageRegistryExt.setStatusBarMessage(text, arg); }, showInputBox(options?: theia.InputBoxOptions, token?: theia.CancellationToken): PromiseLike { - if (token) { - const coreEvent = Object.assign(token.onCancellationRequested, { maxListeners: 0 }); - const coreCancellationToken = { isCancellationRequested: token.isCancellationRequested, onCancellationRequested: coreEvent }; - return quickOpenExt.showInput(options, coreCancellationToken); - } else { - return quickOpenExt.showInput(options); - } + return quickOpenExt.showInput(options, token); }, createStatusBarItem(alignment?: theia.StatusBarAlignment, priority?: number): theia.StatusBarItem { return statusBarMessageRegistryExt.createStatusBarItem(alignment, priority); diff --git a/packages/plugin-ext/src/plugin/quick-open.ts b/packages/plugin-ext/src/plugin/quick-open.ts index f045b3474bc08..824719f6f4fd9 100644 --- a/packages/plugin-ext/src/plugin/quick-open.ts +++ b/packages/plugin-ext/src/plugin/quick-open.ts @@ -14,11 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { QuickOpenExt, PLUGIN_RPC_CONTEXT as Ext, QuickOpenMain, TransferInputBox, Plugin, TransferQuickPick, QuickInputTitleButtonHandle } from '../common/plugin-api-rpc'; +import * as theia from '@theia/plugin'; import { QuickPickOptions, QuickPickItem, InputBoxOptions, InputBox, QuickPick, QuickInput } from '@theia/plugin'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; import { RPCProtocol } from '../common/rpc-protocol'; -import { anyPromise } from '../common/async-util'; -import { hookCancellationToken } from '../common/async-util'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { QuickInputButtons, QuickInputButton, ThemeIcon } from './types-impl'; @@ -53,14 +52,16 @@ export class QuickOpenExtImpl implements QuickOpenExt { return undefined; } - showQuickPick(promiseOrItems: QuickPickItem[] | PromiseLike, options?: QuickPickOptions, token?: CancellationToken): PromiseLike; - // eslint-disable-next-line max-len - showQuickPick(promiseOrItems: QuickPickItem[] | PromiseLike, options?: QuickPickOptions & { canSelectMany: true; }, token?: CancellationToken): PromiseLike; - showQuickPick(promiseOrItems: string[] | PromiseLike, options?: QuickPickOptions, token?: CancellationToken): PromiseLike; - // eslint-disable-next-line max-len - showQuickPick(promiseOrItems: Item[] | PromiseLike, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): PromiseLike { + /* eslint-disable max-len */ + showQuickPick(promiseOrItems: QuickPickItem[] | PromiseLike, options?: QuickPickOptions, token?: theia.CancellationToken): PromiseLike; + showQuickPick(promiseOrItems: QuickPickItem[] | PromiseLike, options?: QuickPickOptions & { canSelectMany: true; }, token?: theia.CancellationToken): PromiseLike; + showQuickPick(promiseOrItems: string[] | PromiseLike, options?: QuickPickOptions, token?: theia.CancellationToken): PromiseLike; + showQuickPick(itemsOrItemsPromise: Item[] | PromiseLike, options?: QuickPickOptions, token: theia.CancellationToken = CancellationToken.None): PromiseLike { + /* eslint-enable max-len */ this.selectItemHandler = undefined; - const itemPromise = Promise.resolve(promiseOrItems); + + const itemsPromise = >Promise.resolve(itemsOrItemsPromise); + const widgetPromise = this.proxy.$show({ canSelectMany: options && options.canPickMany, placeHolder: options && options.placeHolder, @@ -68,13 +69,16 @@ export class QuickOpenExtImpl implements QuickOpenExt { matchOnDescription: options && options.matchOnDescription, matchOnDetail: options && options.matchOnDetail, ignoreFocusLost: options && options.ignoreFocusOut - }); + }, token); + + const widgetClosedMarker = {}; + const widgetClosedPromise = widgetPromise.then(() => widgetClosedMarker); - const promise = anyPromise([]>[widgetPromise, itemPromise]).then(values => { - if (values.key === 0) { + return Promise.race([widgetClosedPromise, itemsPromise]).then(result => { + if (result === widgetClosedMarker) { return undefined; } - return itemPromise.then(items => { + return itemsPromise.then(items => { const pickItems = quickPickItemToPickOpenItem(items); @@ -99,12 +103,8 @@ export class QuickOpenExtImpl implements QuickOpenExt { } return undefined; }); - }, err => { - this.proxy.$setError(err); - return Promise.reject(err); }); }); - return hookCancellationToken(token, promise); } showCustomQuickPick(options: TransferQuickPick): void { @@ -118,7 +118,7 @@ export class QuickOpenExtImpl implements QuickOpenExt { return newQuickInput; } - showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): PromiseLike { + showInput(options?: InputBoxOptions, token: theia.CancellationToken = CancellationToken.None): PromiseLike { this.validateInputHandler = options && options.validateInput; if (!options) { @@ -127,8 +127,7 @@ export class QuickOpenExtImpl implements QuickOpenExt { }; } - const promise = this.proxy.$input(options, typeof this.validateInputHandler === 'function'); - return hookCancellationToken(token, promise); + return this.proxy.$input(options, typeof this.validateInputHandler === 'function', token); } hide(): void {