Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Plugin-API] Apply window.createQuickPick() #5012

Merged
merged 1 commit into from
May 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
132 changes: 132 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/lib/common/disposable';

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,128 @@ export class QuickOpenExtImpl implements QuickOpenExt {
}

}

/**
* Base implementation of {@link QuickPick} that uses {@link QuickOpenExt}.
* Missing functionality is going to be implemented in the scope of https://github.com/theia-ide/theia/issues/5059
*/
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._items = [];
this._activeItems = [];
this._placeholder = '';
this.buttons = [];
this.step = 0;
this.title = '';
this.totalSteps = 0;
this.value = '';
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 hide = () => {
this.onDidHideEmitter.fire(undefined);
};
const selectItem = (item: T) => {
this.activeItems = [item];
this.onDidAcceptEmitter.fire(undefined);
this.onDidChangeSelectionEmitter.fire([item]);
};
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);
}
hide();
}, 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