Skip to content

Commit

Permalink
[Plugin-API] Apply window.createQuickPick()
Browse files Browse the repository at this point in the history
Signed-off-by: Igor Vinokur <ivinokur@redhat.com>
  • Loading branch information
vinokurig committed Apr 26, 2019
1 parent 6366cda commit cb828da
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import { WebviewsExtImpl } from './webviews';
import { TasksExtImpl } from './tasks/tasks';
import { DebugExtImpl } from './node/debug/debug';
import { FileSystemExtImpl } from './file-system';
import { QuickPick, QuickPickItem } from '@theia/plugin';

export function createAPIFactory(
rpc: RPCProtocol,
Expand Down Expand Up @@ -271,6 +272,9 @@ export function createAPIFactory(
return quickOpenExt.showQuickPick(items, options);
}
},
createQuickPick<T extends QuickPickItem>(): QuickPick<T> {
return quickOpenExt.createQuickPick();
},
showWorkspaceFolderPick(options?: theia.WorkspaceFolderPickOptions): PromiseLike<theia.WorkspaceFolder | undefined> {
return workspaceExt.pickWorkspaceFolder(options);
},
Expand Down
120 changes: 120 additions & 0 deletions packages/plugin-ext/src/plugin/quick-open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { CancellationToken } from '@theia/core/lib/common/cancellation';
import { RPCProtocol } from '../api/rpc-protocol';
import { anyPromise } from '../api/async-util';
import { hookCancellationToken } from '../api/async-util';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { QuickPick, QuickInputButton } from '@theia/plugin';
import { DisposableCollection } from '@theia/core';

export type Item = string | QuickPickItem;

Expand Down Expand Up @@ -109,6 +112,10 @@ export class QuickOpenExtImpl implements QuickOpenExt {
return hookCancellationToken<Item | Item[] | undefined>(token, promise);
}

createQuickPick<T extends QuickPickItem>(): QuickPick<T> {
return new QuickPickExt(this);
}

showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): PromiseLike<string | undefined> {
this.validateInputHandler = options && options.validateInput;

Expand All @@ -123,3 +130,116 @@ export class QuickOpenExtImpl implements QuickOpenExt {
}

}

/**
* Base implementation of {@link QuickPick} that uses {@link QuickOpenExt}.
*/
export class QuickPickExt<T extends QuickPickItem> implements QuickPick<T> {

busy: boolean;
buttons: ReadonlyArray<QuickInputButton>;
canSelectMany: boolean;
enabled: boolean;
ignoreFocusOut: boolean;
matchOnDescription: boolean;
matchOnDetail: boolean;
selectedItems: ReadonlyArray<T>;
step: number | undefined;
title: string | undefined;
totalSteps: number | undefined;
value: string;

private _items: T[];
private _activeItems: T[];
private _placeholder: string | undefined;
private disposableCollection: DisposableCollection;
private readonly onDidHideEmitter: Emitter<void>;
private readonly onDidAcceptEmitter: Emitter<void>;
private readonly onDidChangeActiveEmitter: Emitter<T[]>;
private readonly onDidChangeSelectionEmitter: Emitter<T[]>;
private readonly onDidChangeValueEmitter: Emitter<string>;
private readonly onDidTriggerButtonEmitter: Emitter<QuickInputButton>;

constructor(readonly quickOpen: QuickOpenExtImpl) {
this.disposableCollection = new DisposableCollection();
this.disposableCollection.push(this.onDidHideEmitter = new Emitter());
this.disposableCollection.push(this.onDidAcceptEmitter = new Emitter());
this.disposableCollection.push(this.onDidChangeActiveEmitter = new Emitter());
this.disposableCollection.push(this.onDidChangeSelectionEmitter = new Emitter());
this.disposableCollection.push(this.onDidChangeValueEmitter = new Emitter());
this.disposableCollection.push(this.onDidTriggerButtonEmitter = new Emitter());
}

get items(): T[] {
return this._items;
}

set items(activeItems: T[]) {
this._items = activeItems;
}

get activeItems(): T[] {
return this._activeItems;
}

set activeItems(activeItems: T[]) {
this._activeItems = activeItems;
}

get onDidAccept(): Event<void> {
return this.onDidAcceptEmitter.event;
}

get placeholder(): string | undefined {
return this._placeholder;
}
set placeholder(placeholder: string | undefined) {
this._placeholder = placeholder;
}

get onDidChangeActive(): Event<T[]> {
return this.onDidChangeActiveEmitter.event;
}

get onDidChangeSelection(): Event<T[]> {
return this.onDidChangeSelectionEmitter.event;
}

get onDidChangeValue(): Event<string> {
return this.onDidChangeValueEmitter.event;
}

get onDidTriggerButton(): Event<QuickInputButton> {
return this.onDidTriggerButtonEmitter.event;
}

get onDidHide(): Event<void> {
return this.onDidHideEmitter.event;
}

dispose(): void {
this.disposableCollection.dispose();
}

hide(): void {
this.dispose();
}

show(): void {
const selectItem = (item: T) => {
this.activeItems = [item];
this.onDidAcceptEmitter.fire(undefined);
this.onDidHideEmitter.fire(undefined);
this.dispose();
};
this.quickOpen.showQuickPick(this.items.map(item => item as T), {
// tslint:disable-next-line:no-any
onDidSelectItem(item: T | string): any {
if (typeof item !== 'string') {
selectItem(item);
}
}, placeHolder: this.placeholder
});
}

}
201 changes: 201 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,96 @@ declare module '@theia/plugin' {
provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult<string>;
}

/**
* A light-weight user input UI that is initially not visible. After
* configuring it through its properties the extension can make it
* visible by calling [QuickInput.show](#QuickInput.show).
*
* There are several reasons why this UI might have to be hidden and
* the extension will be notified through [QuickInput.onDidHide](#QuickInput.onDidHide).
* (Examples include: an explicit call to [QuickInput.hide](#QuickInput.hide),
* the user pressing Esc, some other input UI opening, etc.)
*
* A user pressing Enter or some other gesture implying acceptance
* of the current state does not automatically hide this UI component.
* It is up to the extension to decide whether to accept the user's input
* and if the UI should indeed be hidden through a call to [QuickInput.hide](#QuickInput.hide).
*
* When the extension no longer needs this input UI, it should
* [QuickInput.dispose](#QuickInput.dispose) it to allow for freeing up
* any resources associated with it.
*
* See [QuickPick](#QuickPick) and [InputBox](#InputBox) for concrete UIs.
*/
export interface QuickInput {

/**
* An optional title.
*/
title: string | undefined;

/**
* An optional current step count.
*/
step: number | undefined;

/**
* An optional total step count.
*/
totalSteps: number | undefined;

/**
* If the UI should allow for user input. Defaults to true.
*
* Change this to false, e.g., while validating user input or
* loading data for the next step in user input.
*/
enabled: boolean;

/**
* If the UI should show a progress indicator. Defaults to false.
*
* Change this to true, e.g., while loading more data or validating
* user input.
*/
busy: boolean;

/**
* If the UI should stay open even when loosing UI focus. Defaults to false.
*/
ignoreFocusOut: boolean;

/**
* Makes the input UI visible in its current configuration. Any other input
* UI will first fire an [QuickInput.onDidHide](#QuickInput.onDidHide) event.
*/
show(): void;

/**
* Hides this input UI. This will also fire an [QuickInput.onDidHide](#QuickInput.onDidHide)
* event.
*/
hide(): void;

/**
* An event signaling when this input UI is hidden.
*
* There are several reasons why this UI might have to be hidden and
* the extension will be notified through [QuickInput.onDidHide](#QuickInput.onDidHide).
* (Examples include: an explicit call to [QuickInput.hide](#QuickInput.hide),
* the user pressing Esc, some other input UI opening, etc.)
*/
onDidHide: Event<void>;

/**
* Dispose of this input UI and any associated resources. If it is still
* visible, it is first hidden. After this call the input UI is no longer
* functional and no additional methods or properties on it should be
* accessed. Instead a new input UI should be created.
*/
dispose(): void;
}

/**
* Something that can be selected from a list of items.
*/
Expand Down Expand Up @@ -1879,6 +1969,105 @@ declare module '@theia/plugin' {
picked?: boolean;
}

/**
* Button for an action in a [QuickPick](#QuickPick) or [InputBox](#InputBox).
*/
export interface QuickInputButton {

/**
* Icon for the button.
*/
readonly iconPath: Uri | { light: Uri; dark: Uri } | ThemeIcon;

/**
* An optional tooltip.
*/
readonly tooltip?: string | undefined;
}

/**
* A concrete [QuickInput](#QuickInput) to let the user pick an item from a
* list of items of type T. The items can be filtered through a filter text field and
* there is an option [canSelectMany](#QuickPick.canSelectMany) to allow for
* selecting multiple items.
*
* Note that in many cases the more convenient [window.showQuickPick](#window.showQuickPick)
* is easier to use. [window.createQuickPick](#window.createQuickPick) should be used
* when [window.showQuickPick](#window.showQuickPick) does not offer the required flexibility.
*/
export interface QuickPick<T extends QuickPickItem> extends QuickInput {

/**
* Current value of the filter text.
*/
value: string;

/**
* Optional placeholder in the filter text.
*/
placeholder: string | undefined;

/**
* An event signaling when the value of the filter text has changed.
*/
readonly onDidChangeValue: Event<string>;

/**
* An event signaling when the user indicated acceptance of the selected item(s).
*/
readonly onDidAccept: Event<void>;

/**
* Buttons for actions in the UI.
*/
buttons: ReadonlyArray<QuickInputButton>;

/**
* An event signaling when a button was triggered.
*/
readonly onDidTriggerButton: Event<QuickInputButton>;

/**
* Items to pick from.
*/
items: ReadonlyArray<T>;

/**
* If multiple items can be selected at the same time. Defaults to false.
*/
canSelectMany: boolean;

/**
* If the filter text should also be matched against the description of the items. Defaults to false.
*/
matchOnDescription: boolean;

/**
* If the filter text should also be matched against the detail of the items. Defaults to false.
*/
matchOnDetail: boolean;

/**
* Active items. This can be read and updated by the extension.
*/
activeItems: ReadonlyArray<T>;

/**
* An event signaling when the active items have changed.
*/
readonly onDidChangeActive: Event<T[]>;

/**
* Selected items. This can be read and updated by the extension.
*/
selectedItems: ReadonlyArray<T>;

/**
* An event signaling when the selected items have changed.
*/
readonly onDidChangeSelection: Event<T[]>;
}

/**
* Options for configuration behavior of the quick pick
*/
Expand Down Expand Up @@ -2885,6 +3074,18 @@ declare module '@theia/plugin' {
token?: CancellationToken
): PromiseLike<T[] | undefined>;

/**
* Creates a [QuickPick](#QuickPick) to let the user pick an item from a list
* of items of type T.
*
* Note that in many cases the more convenient [window.showQuickPick](#window.showQuickPick)
* is easier to use. [window.createQuickPick](#window.createQuickPick) should be used
* when [window.showQuickPick](#window.showQuickPick) does not offer the required flexibility.
*
* @return A new [QuickPick](#QuickPick).
*/
export function createQuickPick<T extends QuickPickItem>(): QuickPick<T>;

/**
* Shows a selection list of [workspace folders](#workspace.workspaceFolders) to pick from.
* Returns `undefined` if no folder is open.
Expand Down

0 comments on commit cb828da

Please sign in to comment.