diff --git a/packages/core/src/browser/keybinding.ts b/packages/core/src/browser/keybinding.ts index b7e48e271120c..e2405af0e2472 100644 --- a/packages/core/src/browser/keybinding.ts +++ b/packages/core/src/browser/keybinding.ts @@ -24,6 +24,7 @@ import { ContributionProvider } from '../common/contribution-provider'; import { ILogger } from '../common/logger'; import { StatusBarAlignment, StatusBar } from './status-bar/status-bar'; import { ContextKeyService } from './context-key-service'; +import * as common from '../common/keybinding'; export enum KeybindingScope { DEFAULT, @@ -60,22 +61,10 @@ export namespace Keybinding { } } -export interface Keybinding { - /** Command identifier, this needs to be a unique string. */ - command: string; - /** Keybinding string as defined in packages/keymaps/README.md. */ - keybinding: string; - /** - * The optional keybinding context where this binding belongs to. - * If not specified, then this keybinding context belongs to the NOOP - * keybinding context. - */ - context?: string; - /** - * https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts - */ - when?: string; -} +/** + * @deprecated import from `@theia/core/lib/common/keybinding` instead + */ +export type Keybinding = common.Keybinding; export interface ResolvedKeybinding extends Keybinding { /** 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 a3cbc41efa7b2..c6d6e125f144e 100644 --- a/packages/core/src/browser/quick-open/quick-input-service.ts +++ b/packages/core/src/browser/quick-open/quick-input-service.ts @@ -21,7 +21,8 @@ import { Deferred } from '../../common/promise-util'; import { MaybePromise } from '../../common/types'; import { MessageType } from '../../common/message-service-protocol'; import { Emitter, Event } from '../../common/event'; -import { QuickTitleBar, QuickInputTitleButton } from './quick-title-bar'; +import { QuickTitleBar } from './quick-title-bar'; +import { QuickTitleButton } from '../../common/quick-open-model'; export interface QuickInputOptions { @@ -53,7 +54,7 @@ export interface QuickInputOptions { /** * Buttons that are displayed on the title panel */ - buttons?: ReadonlyArray + buttons?: ReadonlyArray /** * Text for when there is a problem with the current input value @@ -119,10 +120,6 @@ export class QuickInputService { let currentText = ''; const validateInput = options && options.validateInput; - if (options && this.quickTitleBar.shouldShowTitleBar(options.title, options.step)) { - this.quickTitleBar.attachTitleBar(this.quickOpenService.widgetNode, options.title, options.step, options.totalSteps, options.buttons); - } - this.quickOpenService.open({ onType: async (lookFor, acceptor) => { this.onDidChangeValueEmitter.fire(lookFor); @@ -159,10 +156,15 @@ export class QuickInputService { 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); + } + return result.promise; } - refresh() { + refresh(): void { this.quickOpenService.refresh(); } diff --git a/packages/core/src/browser/quick-open/quick-open-action-provider.ts b/packages/core/src/browser/quick-open/quick-open-action-provider.ts index d5deda316dccf..f666bc5da9481 100644 --- a/packages/core/src/browser/quick-open/quick-open-action-provider.ts +++ b/packages/core/src/browser/quick-open/quick-open-action-provider.ts @@ -14,28 +14,24 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Disposable } from '../../common/disposable'; import { injectable } from 'inversify'; -import { QuickOpenItem } from './quick-open-model'; - -export interface QuickOpenActionProvider { - hasActions(item: QuickOpenItem): boolean; - getActions(item: QuickOpenItem): Promise; -} - -export interface QuickOpenActionOptions { - id: string; - label?: string; - tooltip?: string; - class?: string | undefined; - enabled?: boolean; - checked?: boolean; - radio?: boolean; -} - -export interface QuickOpenAction extends QuickOpenActionOptions, Disposable { - run(item?: QuickOpenItem): PromiseLike; -} +import { QuickOpenItem } from '../../common/quick-open-model'; +import * as common from '../../common/quick-open-model'; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type QuickOpenActionProvider = common.QuickOpenActionProvider; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type QuickOpenActionOptions = common.QuickOpenActionOptions; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type QuickOpenAction = common.QuickOpenAction; @injectable() export abstract class QuickOpenBaseAction implements QuickOpenAction { diff --git a/packages/core/src/browser/quick-open/quick-open-model.ts b/packages/core/src/browser/quick-open/quick-open-model.ts index 7b66e1046d456..0f04e4e61622b 100644 --- a/packages/core/src/browser/quick-open/quick-open-model.ts +++ b/packages/core/src/browser/quick-open/quick-open-model.ts @@ -14,97 +14,29 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import URI from '../../common/uri'; -import { Keybinding } from '../keybinding'; -import { QuickOpenActionProvider } from './quick-open-action-provider'; - -export interface Highlight { - start: number - end: number -} - -export enum QuickOpenMode { - PREVIEW, - OPEN, - OPEN_IN_BACKGROUND -} - -export interface QuickOpenItemOptions { - tooltip?: string; - label?: string; - labelHighlights?: Highlight[]; - description?: string; - descriptionHighlights?: Highlight[]; - detail?: string; - detailHighlights?: Highlight[]; - hidden?: boolean; - uri?: URI; - iconClass?: string; - keybinding?: Keybinding; - run?(mode: QuickOpenMode): boolean; -} -export interface QuickOpenGroupItemOptions extends QuickOpenItemOptions { - groupLabel?: string; - showBorder?: boolean; -} - -export class QuickOpenItem { - - constructor( - protected options: T = {} as T - ) { } - - getTooltip(): string | undefined { - return this.options.tooltip || this.getLabel(); - } - getLabel(): string | undefined { - return this.options.label; - } - getLabelHighlights(): Highlight[] { - return this.options.labelHighlights || []; - } - getDescription(): string | undefined { - return this.options.description; - } - getDescriptionHighlights(): Highlight[] | undefined { - return this.options.descriptionHighlights; - } - getDetail(): string | undefined { - return this.options.detail; - } - getDetailHighlights(): Highlight[] | undefined { - return this.options.detailHighlights; - } - isHidden(): boolean { - return this.options.hidden || false; - } - getUri(): URI | undefined { - return this.options.uri; - } - getIconClass(): string | undefined { - return this.options.iconClass; - } - getKeybinding(): Keybinding | undefined { - return this.options.keybinding; - } - run(mode: QuickOpenMode): boolean { - if (!this.options.run) { - return false; - } - return this.options.run(mode); - } -} - -export class QuickOpenGroupItem extends QuickOpenItem { - - getGroupLabel(): string | undefined { - return this.options.groupLabel; - } - showBorder(): boolean { - return this.options.showBorder || false; - } -} - -export interface QuickOpenModel { - onType(lookFor: string, acceptor: (items: QuickOpenItem[], actionProvider?: QuickOpenActionProvider) => void): void; -} +import * as common from '../../common/quick-open-model'; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type Highlight = common.Highlight; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type QuickOpenItemOptions = common.QuickOpenItemOptions; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type QuickOpenGroupItemOptions = common.QuickOpenGroupItemOptions; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export { QuickOpenItem, QuickOpenGroupItem, QuickOpenMode } from '../../common/quick-open-model'; + +/** + * @deprecated import from `@theia/core/lib/common/quick-open-model` instead + */ +export type QuickOpenModel = common.QuickOpenModel; diff --git a/packages/core/src/browser/quick-open/quick-pick-service-impl.ts b/packages/core/src/browser/quick-open/quick-pick-service-impl.ts index 0fcfd3665f860..38adbe068299e 100644 --- a/packages/core/src/browser/quick-open/quick-pick-service-impl.ts +++ b/packages/core/src/browser/quick-open/quick-pick-service-impl.ts @@ -31,23 +31,27 @@ export class QuickPickServiceImpl implements QuickPickService { @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService; + private items: QuickOpenItem[] = []; + show(elements: string[], options?: QuickPickOptions): Promise; show(elements: QuickPickItem[], options?: QuickPickOptions): Promise; async show(elements: (string | QuickPickItem)[], options?: QuickPickOptions): Promise { return new Promise(resolve => { - const items = this.toItems(elements, resolve); - if (items.length === 1) { - items[0].run(QuickOpenMode.OPEN); + this.items = this.toItems(elements, resolve); + if (this.items.length === 1) { + this.items[0].run(QuickOpenMode.OPEN); return; } - if (options && this.quickTitleBar.shouldShowTitleBar(options.title, options.step)) { - this.quickTitleBar.attachTitleBar(this.quickOpenService.widgetNode, options.title, options.step, options.totalSteps, options.buttons); - } const prefix = options && options.value ? options.value : ''; + let savedValue: string; this.quickOpenService.open({ - onType: (_, acceptor) => { - acceptor(items); - this.onDidChangeActiveItemsEmitter.fire(items); + onType: (value, acceptor) => { + acceptor(this.items); + if (savedValue !== value) { + this.onDidChangeValueEmitter.fire(value); + this.onDidChangeActiveItemsEmitter.fire(this.items); + savedValue = value; + } } }, Object.assign({ onClose: () => { @@ -58,6 +62,9 @@ export class QuickPickServiceImpl implements QuickPickService { fuzzyMatchDescription: true, prefix }, options)); + if (options && this.quickTitleBar.shouldShowTitleBar(options.title, options.step)) { + this.quickTitleBar.attachTitleBar(this.quickOpenService.widgetNode, options.title, options.step, options.totalSteps, options.buttons); + } }); } protected toItems(elements: (string | QuickPickItem)[], resolve: (element: Object) => void): QuickOpenItem[] { @@ -110,4 +117,11 @@ export class QuickPickServiceImpl implements QuickPickService { private readonly onDidChangeActiveItemsEmitter: Emitter[]> = new Emitter[]>(); readonly onDidChangeActiveItems: Event[]> = this.onDidChangeActiveItemsEmitter.event; + private readonly onDidChangeValueEmitter: Emitter = new Emitter(); + readonly onDidChangeValue: Event = this.onDidChangeValueEmitter.event; + + setItems(items: QuickOpenItem[]): void { + this.items = items; + this.quickOpenService.refresh(); + } } diff --git a/packages/core/src/browser/quick-open/quick-title-bar.ts b/packages/core/src/browser/quick-open/quick-title-bar.ts index cc86a9993f2dd..00d764eadcaff 100644 --- a/packages/core/src/browser/quick-open/quick-title-bar.ts +++ b/packages/core/src/browser/quick-open/quick-title-bar.ts @@ -14,26 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Emitter } from '../../common/event'; -import { DisposableCollection } from '../../common/disposable'; +import { Emitter, Event } from '../../common/event'; import { injectable } from 'inversify'; - -export enum QuickInputTitleButtonSide { - LEFT = 0, - RIGHT = 1 -} - -export interface QuickInputTitleButton { - icon: string; // a background image coming from a url - iconClass?: string; // a class such as one coming from font awesome - tooltip?: string | undefined; - side: QuickInputTitleButtonSide -} +import { QuickTitleButton, QuickTitleButtonSide } from '../../common/quick-open-model'; @injectable() export class QuickTitleBar { - private readonly onDidTriggerButtonEmitter: Emitter; + private readonly onDidTriggerButtonEmitter: Emitter; private _isAttached: boolean; private titleElement: HTMLElement; @@ -43,21 +31,18 @@ export class QuickTitleBar { private _title: string | undefined; private _step: number | undefined; private _totalSteps: number | undefined; - private _buttons: ReadonlyArray; + private _buttons: ReadonlyArray; private tabIndex = 2; // Keep track of the tabIndex for the buttons - private disposableCollection: DisposableCollection; constructor() { - this.titleElement = document.createElement('h3'); - this.titleElement.style.textAlign = 'center'; - this.titleElement.style.margin = '0'; + this.titleElement = document.createElement('div'); + this.titleElement.className = QuickTitleBar.Styles.QUICK_TITLE_HEADER; - this.disposableCollection = new DisposableCollection(); - this.disposableCollection.push(this.onDidTriggerButtonEmitter = new Emitter()); + this.onDidTriggerButtonEmitter = new Emitter(); } - get onDidTriggerButton() { + get onDidTriggerButton(): Event { return this.onDidTriggerButtonEmitter.event; } @@ -96,7 +81,7 @@ export class QuickTitleBar { return this._totalSteps; } - set buttons(buttons: ReadonlyArray | undefined) { + set buttons(buttons: ReadonlyArray | undefined) { if (buttons === undefined) { this._buttons = []; return; @@ -105,7 +90,7 @@ export class QuickTitleBar { this._buttons = buttons; } - get buttons() { + get buttons(): ReadonlyArray | undefined { return this._buttons; } @@ -117,7 +102,7 @@ export class QuickTitleBar { } if (this.step && this.totalSteps) { - innerTitle += `(${this.step} / ${this.totalSteps})`; + innerTitle += `(${this.step}/${this.totalSteps})`; } else if (this.step) { innerTitle += this.step; } @@ -126,66 +111,52 @@ export class QuickTitleBar { } // Left buttons are for the buttons dervied from QuickInputButtons - private getLeftButtons() { + private getLeftButtons(): ReadonlyArray { if (this._buttons === undefined || this._buttons.length === 0) { return []; } - return this._buttons.filter(btn => btn.side === QuickInputTitleButtonSide.LEFT); + return this._buttons.filter(btn => btn.side === QuickTitleButtonSide.LEFT); } - private getRightButtons() { + private getRightButtons(): ReadonlyArray { if (this._buttons === undefined || this._buttons.length === 0) { return []; } - return this._buttons.filter(btn => btn.side === QuickInputTitleButtonSide.RIGHT); + return this._buttons.filter(btn => btn.side === QuickTitleButtonSide.RIGHT); } - private createButtonElement(buttons: ReadonlyArray) { - const buttonDiv = document.createElement('div'); - buttonDiv.style.display = 'inline-flex'; - for (const btn of buttons) { - const aElement = document.createElement('a'); - aElement.style.width = '16px'; - aElement.style.height = '16px'; - aElement.tabIndex = 0; + private createButtonElements(buttons: ReadonlyArray): HTMLSpanElement[] { + return buttons.map(btn => { + const spanElement = document.createElement('span'); + spanElement.className = QuickTitleBar.Styles.QUICK_TITLE_BUTTON; + spanElement.tabIndex = 0; if (btn.iconClass) { - aElement.classList.add(...btn.iconClass.split(' ')); + spanElement.classList.add(...btn.iconClass.split(' ')); } if (btn.icon !== '') { - aElement.style.backgroundImage = `url(\'${btn.icon}\')`; + spanElement.style.backgroundImage = `url(\'${btn.icon}\')`; } - aElement.classList.add('icon'); - aElement.style.display = 'flex'; - aElement.style.justifyContent = 'center'; - aElement.style.alignItems = 'center'; - aElement.style.cursor = 'pointer'; - aElement.tabIndex = this.tabIndex; - aElement.title = btn.tooltip ? btn.tooltip : ''; - aElement.onclick = () => { + spanElement.classList.add('icon'); + spanElement.tabIndex = this.tabIndex; + spanElement.title = btn.tooltip ? btn.tooltip : ''; + spanElement.onclick = () => { this.onDidTriggerButtonEmitter.fire(btn); }; - aElement.onkeyup = event => { + spanElement.onkeyup = event => { if (event.code === 'Enter') { - aElement.click(); + spanElement.click(); } }; - buttonDiv.appendChild(aElement); this.tabIndex += 1; - } - return buttonDiv; + return spanElement; + }); } - private createTitleBarDiv() { + private createTitleBarDiv(): HTMLDivElement { const div = document.createElement('div'); - div.style.display = 'flex'; - div.style.flexDirection = 'row'; - div.style.fontSize = '13px'; - div.style.padding = '0px 1px'; - div.style.justifyContent = 'flex-start'; - div.style.alignItems = 'center'; - div.style.background = 'var(--theia-layout-color4)'; + div.className = QuickTitleBar.Styles.QUICK_TITLE_CONTAINER; div.onclick = event => { event.stopPropagation(); event.preventDefault(); @@ -193,26 +164,24 @@ export class QuickTitleBar { return div; } - private createLeftButtonDiv() { + private createLeftButtonDiv(): HTMLDivElement { const leftButtonDiv = document.createElement('div'); // Holds all the buttons that get added to the left - leftButtonDiv.style.flex = '1'; - leftButtonDiv.style.textAlign = 'left'; + leftButtonDiv.className = QuickTitleBar.Styles.QUICK_TITLE_LEFT_BAR; - leftButtonDiv.appendChild(this.createButtonElement(this.getLeftButtons())); + this.createButtonElements(this.getLeftButtons()).forEach(btn => leftButtonDiv.appendChild(btn)); return leftButtonDiv; } - private createRightButtonDiv() { + private createRightButtonDiv(): HTMLDivElement { const rightButtonDiv = document.createElement('div'); - rightButtonDiv.style.flex = '1'; - rightButtonDiv.style.textAlign = 'right'; + rightButtonDiv.className = QuickTitleBar.Styles.QUICK_TITLE_RIGHT_BAR; - rightButtonDiv.appendChild(this.createButtonElement(this.getRightButtons())); + this.createButtonElements(this.getRightButtons()).forEach(btn => rightButtonDiv.appendChild(btn)); return rightButtonDiv; } // tslint:disable-next-line:max-line-length - public attachTitleBar(widgetNode: HTMLElement, title: string | undefined, step: number | undefined, totalSteps: number | undefined, buttons: ReadonlyArray | undefined) { + public attachTitleBar(widgetNode: HTMLElement, title: string | undefined, step: number | undefined, totalSteps: number | undefined, buttons: ReadonlyArray | undefined): void { const div = this.createTitleBarDiv(); this.updateInnerTitleText(); @@ -236,7 +205,7 @@ export class QuickTitleBar { this.isAttached = true; } - hide() { + hide(): void { this.title = undefined; this.buttons = undefined; this.step = undefined; @@ -252,8 +221,14 @@ export class QuickTitleBar { return ((title !== undefined) || (step !== undefined)); } - dispose() { - this.disposableCollection.dispose(); - } +} +export namespace QuickTitleBar { + export namespace Styles { + export const QUICK_TITLE_CONTAINER = 'theia-quick-title-container'; + export const QUICK_TITLE_LEFT_BAR = 'theia-quick-title-left-bar'; + export const QUICK_TITLE_RIGHT_BAR = 'theia-quick-title-right-bar'; + export const QUICK_TITLE_HEADER = 'theia-quick-title-header'; + export const QUICK_TITLE_BUTTON = 'theia-quick-title-button'; + } } diff --git a/packages/core/src/browser/style/index.css b/packages/core/src/browser/style/index.css index 68f19284eb438..e41a39c1ea078 100644 --- a/packages/core/src/browser/style/index.css +++ b/packages/core/src/browser/style/index.css @@ -200,3 +200,4 @@ textarea { @import './alert-messages.css'; @import './icons.css'; @import './widget.css'; +@import './quick-title-bar.css'; diff --git a/packages/core/src/browser/style/quick-title-bar.css b/packages/core/src/browser/style/quick-title-bar.css new file mode 100644 index 0000000000000..a90046adf3882 --- /dev/null +++ b/packages/core/src/browser/style/quick-title-bar.css @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (C) 2019 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 + ********************************************************************************/ + +.theia-quick-title-container { + display: flex; + padding: calc(var(--theia-ui-padding)/2); + justify-content: space-between; + align-items: center; + background: var(--theia-layout-color4); +} + +.theia-quick-title-left-bar { + display: flex; + text-align: left; +} + +.theia-quick-title-right-bar { + display: flex; + text-align: right; +} + +.theia-quick-title-header { + text-align: center; +} + +.theia-quick-title-button { + width: 16px; + height: 16px; + display: flex; + align-items: center; + cursor: pointer; +} diff --git a/packages/core/src/common/keybinding.ts b/packages/core/src/common/keybinding.ts new file mode 100644 index 0000000000000..98791f0fff496 --- /dev/null +++ b/packages/core/src/common/keybinding.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (C) 2017 TypeFox 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 + ********************************************************************************/ + +export interface Keybinding { + /** Command identifier, this needs to be a unique string. */ + command: string; + /** Keybinding string as defined in packages/keymaps/README.md. */ + keybinding: string; + /** + * The optional keybinding context where this binding belongs to. + * If not specified, then this keybinding context belongs to the NOOP + * keybinding context. + */ + context?: string; + /** + * https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts + */ + when?: string; +} diff --git a/packages/core/src/common/quick-open-model.ts b/packages/core/src/common/quick-open-model.ts new file mode 100644 index 0000000000000..1e06f4dd7aad7 --- /dev/null +++ b/packages/core/src/common/quick-open-model.ts @@ -0,0 +1,141 @@ +/******************************************************************************** + * Copyright (C) 2017 TypeFox 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 URI from './uri'; +import { Keybinding } from './keybinding'; +import { Disposable } from './disposable'; + +export interface Highlight { + start: number + end: number +} + +export enum QuickOpenMode { + PREVIEW, + OPEN, + OPEN_IN_BACKGROUND +} + +export interface QuickOpenItemOptions { + tooltip?: string; + label?: string; + labelHighlights?: Highlight[]; + description?: string; + descriptionHighlights?: Highlight[]; + detail?: string; + detailHighlights?: Highlight[]; + hidden?: boolean; + uri?: URI; + iconClass?: string; + keybinding?: Keybinding; + run?(mode: QuickOpenMode): boolean; +} +export interface QuickOpenGroupItemOptions extends QuickOpenItemOptions { + groupLabel?: string; + showBorder?: boolean; +} + +export class QuickOpenItem { + + constructor( + protected options: T = {} as T + ) { } + + getTooltip(): string | undefined { + return this.options.tooltip || this.getLabel(); + } + getLabel(): string | undefined { + return this.options.label; + } + getLabelHighlights(): Highlight[] { + return this.options.labelHighlights || []; + } + getDescription(): string | undefined { + return this.options.description; + } + getDescriptionHighlights(): Highlight[] | undefined { + return this.options.descriptionHighlights; + } + getDetail(): string | undefined { + return this.options.detail; + } + getDetailHighlights(): Highlight[] | undefined { + return this.options.detailHighlights; + } + isHidden(): boolean { + return this.options.hidden || false; + } + getUri(): URI | undefined { + return this.options.uri; + } + getIconClass(): string | undefined { + return this.options.iconClass; + } + getKeybinding(): Keybinding | undefined { + return this.options.keybinding; + } + run(mode: QuickOpenMode): boolean { + if (!this.options.run) { + return false; + } + return this.options.run(mode); + } +} + +export class QuickOpenGroupItem extends QuickOpenItem { + + getGroupLabel(): string | undefined { + return this.options.groupLabel; + } + showBorder(): boolean { + return this.options.showBorder || false; + } +} + +export interface QuickOpenModel { + onType(lookFor: string, acceptor: (items: QuickOpenItem[], actionProvider?: QuickOpenActionProvider) => void): void; +} + +export interface QuickOpenActionProvider { + hasActions(item: QuickOpenItem): boolean; + getActions(item: QuickOpenItem): Promise; +} + +export interface QuickOpenActionOptions { + id: string; + label?: string; + tooltip?: string; + class?: string | undefined; + enabled?: boolean; + checked?: boolean; + radio?: boolean; +} + +export interface QuickOpenAction extends QuickOpenActionOptions, Disposable { + run(item?: QuickOpenItem): PromiseLike; +} + +export enum QuickTitleButtonSide { + LEFT = 0, + RIGHT = 1 +} + +export interface QuickTitleButton { + icon: string; // a background image coming from a url + iconClass?: string; // a class such as one coming from font awesome + tooltip?: string | undefined; + side: QuickTitleButtonSide +} diff --git a/packages/core/src/common/quick-pick-service.ts b/packages/core/src/common/quick-pick-service.ts index 7a9a3aa028c79..f3ee3725a96e8 100644 --- a/packages/core/src/common/quick-pick-service.ts +++ b/packages/core/src/common/quick-pick-service.ts @@ -14,10 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { QuickOpenHideReason } from './quick-open-service'; - -import { QuickInputTitleButton } from '../browser/quick-open/quick-title-bar'; -import { Event } from '../common/event'; -import { QuickOpenItem, QuickOpenItemOptions } from '../browser/quick-open/quick-open-model'; +import { QuickOpenItem, QuickOpenItemOptions, QuickTitleButton } from './quick-open-model'; +import { Event } from './event'; export type QuickPickItem = QuickPickValue | QuickPickSeparator; @@ -68,7 +66,7 @@ export interface QuickPickOptions { /** * Buttons that are displayed on the title panel */ - buttons?: ReadonlyArray + buttons?: ReadonlyArray /** * Set to `true` to keep the input box open when focus moves to another part of the editor or to another window. @@ -89,8 +87,11 @@ export interface QuickPickService { show(elements: QuickPickItem[], options?: QuickPickOptions): Promise; + setItems(items: QuickOpenItem[]): void; + hide(reason?: QuickOpenHideReason): void readonly onDidAccept: Event; + readonly onDidChangeValue: Event; readonly onDidChangeActiveItems: Event[]>; } diff --git a/packages/monaco/src/browser/monaco-quick-open-service.ts b/packages/monaco/src/browser/monaco-quick-open-service.ts index b02a6e5783f9d..a971abf5bfc7e 100644 --- a/packages/monaco/src/browser/monaco-quick-open-service.ts +++ b/packages/monaco/src/browser/monaco-quick-open-service.ts @@ -113,6 +113,9 @@ export class MonacoQuickOpenService extends QuickOpenService { } internalOpen(opts: MonacoQuickOpenControllerOpts): void { + if (this.widgetNode && this.widgetNode.offsetParent !== null) { + this.hide(); + } this.opts = opts; const activeContext = window.document.activeElement || undefined; if (!activeContext || !this.container.contains(activeContext)) { @@ -134,7 +137,7 @@ export class MonacoQuickOpenService extends QuickOpenService { } } - setValueSelected(value: string | undefined, selectLocation: Readonly<[number, number]> | undefined) { + setValueSelected(value: string | undefined, selectLocation: Readonly<[number, number]> | undefined): void { if (!value) { return; } @@ -156,14 +159,14 @@ export class MonacoQuickOpenService extends QuickOpenService { } } - setEnabled(isEnabled: boolean | undefined) { + setEnabled(isEnabled: boolean | undefined): void { const widget = this.widget; if (widget.inputBox) { widget.inputBox.inputElement.readOnly = (isEnabled !== undefined) ? !isEnabled : false; } } - setValue(value: string | undefined) { + setValue(value: string | undefined): void { if (this.widget && this.widget.inputBox) { this.widget.inputBox.inputElement.value = (value !== undefined) ? value : ''; } diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 541bdd32c2c58..dcaf80683839c 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -73,8 +73,8 @@ import { SymbolInformation } from 'vscode-languageserver-types'; import { ScmCommand } from '@theia/scm/lib/browser/scm-provider'; import { ArgumentProcessor } from '../plugin/command-registry'; import { MaybePromise } from '@theia/core/lib/common/types'; -import { QuickInputTitleButton } from '@theia/core/lib/browser/quick-open/quick-title-bar'; import { QuickOpenItem, QuickOpenItemOptions } from '@theia/core/lib/browser/quick-open/quick-open-model'; +import { QuickTitleButton } from '@theia/core/lib/common/quick-open-model'; export interface PluginInitData { plugins: PluginMetadata[]; @@ -304,7 +304,7 @@ export interface QuickOpenExt { $acceptOnDidAccept(quickInputNumber: number): Promise; $acceptDidChangeValue(quickInputNumber: number, changedValue: string): Promise; $acceptOnDidHide(quickInputNumber: number): Promise; - $acceptOnDidTriggerButton(quickInputNumber: number, btn: QuickInputTitleButton): Promise; + $acceptOnDidTriggerButton(quickInputNumber: number, btn: QuickTitleButton): Promise; $acceptDidChangeActive(quickInputNumber: number, changedItems: QuickOpenItem[]): Promise; $acceptDidChangeSelection(quickInputNumber: number, selection: string): Promise; } @@ -393,11 +393,11 @@ export interface WorkspaceFolderPickOptionsMain { ignoreFocusOut?: boolean; } -export interface QuickInputTitleButtonHandle extends QuickInputTitleButton { +export interface QuickInputTitleButtonHandle extends QuickTitleButton { index: number; // index of where they are in buttons array if QuickInputButton or -1 if QuickInputButtons.Back } -export interface ITransferQuickInput { +export interface TransferQuickInput { quickInputIndex: number; title: string | undefined; step: number | undefined; @@ -407,7 +407,7 @@ export interface ITransferQuickInput { ignoreFocusOut: boolean; } -export interface ITransferInputBox extends ITransferQuickInput { +export interface TransferInputBox extends TransferQuickInput { value: string; placeholder: string | undefined; password: boolean; @@ -417,7 +417,7 @@ export interface ITransferInputBox extends ITransferQuickInput { validateInput(value: string): MaybePromise; } -export interface ITransferQuickPick extends ITransferQuickInput { +export interface TransferQuickPick extends TransferQuickInput { value: string; placeholder: string | undefined; buttons: ReadonlyArray; @@ -435,8 +435,8 @@ export interface QuickOpenMain { $setError(error: Error): Promise; $input(options: theia.InputBoxOptions, validateInput: boolean): Promise; $hide(): void; - $showInputBox(inputBox: ITransferInputBox, validateInput: boolean): void; - $showCustomQuickPick(inputBox: ITransferQuickPick): void; + $showInputBox(inputBox: TransferInputBox, validateInput: boolean): void; + $showCustomQuickPick(inputBox: TransferQuickPick): void; $setQuickInputChanged(changed: object): void; $refreshQuickInput(): 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 5f77fab632086..82e84bdfeafc6 100644 --- a/packages/plugin-ext/src/main/browser/quick-open-main.ts +++ b/packages/plugin-ext/src/main/browser/quick-open-main.ts @@ -16,17 +16,32 @@ import { InputBoxOptions, QuickPickItem as QuickPickItemExt } from '@theia/plugin'; import { interfaces } from 'inversify'; -import { QuickOpenModel, QuickOpenItem, QuickOpenMode } from '@theia/core/lib/browser/quick-open/quick-open-model'; +import { + QuickOpenModel, + QuickOpenItem, + QuickOpenMode, + QuickOpenItemOptions +} from '@theia/core/lib/browser/quick-open/quick-open-model'; import { RPCProtocol } from '../../common/rpc-protocol'; -import { QuickOpenExt, QuickOpenMain, MAIN_RPC_CONTEXT, PickOptions, PickOpenItem, ITransferInputBox, QuickInputTitleButtonHandle, ITransferQuickPick } from '../../common/plugin-api-rpc'; +import { + QuickOpenExt, + QuickOpenMain, + MAIN_RPC_CONTEXT, + PickOptions, + PickOpenItem, + TransferInputBox, + QuickInputTitleButtonHandle, + TransferQuickPick +} from '../../common/plugin-api-rpc'; import { MonacoQuickOpenService } from '@theia/monaco/lib/browser/monaco-quick-open-service'; import { QuickInputService, FOLDER_ICON, FILE_ICON } from '@theia/core/lib/browser'; import { PluginSharedStyle } from './plugin-shared-style'; import URI from 'vscode-uri'; import { ThemeIcon, QuickInputButton } from '../../plugin/types-impl'; import { QuickPickService, QuickPickItem, QuickPickValue } from '@theia/core/lib/common/quick-pick-service'; -import { QuickTitleBar, QuickInputTitleButtonSide } from '@theia/core/lib/browser/quick-open/quick-title-bar'; +import { QuickTitleBar } from '@theia/core/lib/browser/quick-open/quick-title-bar'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { QuickTitleButtonSide } from '@theia/core/lib/common/quick-open-model'; export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { @@ -152,7 +167,7 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { icon: newIcon, iconClass: newIconClass, tooltip: quickInputButton.tooltip, - side: isDefaultQuickInputButton ? QuickInputTitleButtonSide.LEFT : QuickInputTitleButtonSide.RIGHT, + side: isDefaultQuickInputButton ? QuickTitleButtonSide.LEFT : QuickTitleButtonSide.RIGHT, index: isDefaultQuickInputButton ? -1 : index }; } @@ -174,7 +189,7 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { } } - $showInputBox(inputBox: ITransferInputBox, validateInput: boolean): void { + $showInputBox(inputBox: TransferInputBox, validateInput: boolean): void { if (validateInput) { inputBox.validateInput = val => this.proxy.$validateInput(val); } @@ -211,7 +226,7 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { } // tslint:disable-next-line:no-any - private findChangedKey(key: string, value: any) { + private findChangedKey(key: string, value: any): void { switch (key) { case 'title': { this.quickTitleBar.title = value; @@ -245,11 +260,15 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { this.delegate.setPlaceHolder(value); break; } + case 'items': { + this.quickPick.setItems(value.map((options: QuickOpenItemOptions) => new QuickOpenItem(options))); + break; + } } } // tslint:disable-next-line:no-any - $setQuickInputChanged(changed: any) { + $setQuickInputChanged(changed: any): void { for (const key in changed) { if (changed.hasOwnProperty(key)) { const value = changed[key]; @@ -257,11 +276,11 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { } } } - $refreshQuickInput() { + $refreshQuickInput(): void { this.quickInput.refresh(); } - $showCustomQuickPick(options: ITransferQuickPick): void { + $showCustomQuickPick(options: TransferQuickPick): void { const items = this.convertPickOpenItemToQuickOpenItem(options.items); const quickPick = this.quickPick.show(items, { buttons: options.buttons.map((btn, i) => this.convertQuickInputButton(btn, i)), @@ -278,6 +297,7 @@ export class QuickOpenMainImpl implements QuickOpenMain, QuickOpenModel { const disposableListeners = new DisposableCollection(); disposableListeners.push(this.quickPick.onDidAccept(() => this.proxy.$acceptOnDidAccept(options.quickInputIndex))); disposableListeners.push(this.quickPick.onDidChangeActiveItems(changedItems => this.proxy.$acceptDidChangeActive(options.quickInputIndex, changedItems))); + disposableListeners.push(this.quickPick.onDidChangeValue(value => this.proxy.$acceptDidChangeValue(options.quickInputIndex, value))); disposableListeners.push(this.quickTitleBar.onDidTriggerButton(button => { this.proxy.$acceptOnDidTriggerButton(options.quickInputIndex, button); })); diff --git a/packages/plugin-ext/src/plugin/quick-open.ts b/packages/plugin-ext/src/plugin/quick-open.ts index c0c771815e4fa..f67188d362c52 100644 --- a/packages/plugin-ext/src/plugin/quick-open.ts +++ b/packages/plugin-ext/src/plugin/quick-open.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { QuickOpenExt, PLUGIN_RPC_CONTEXT as Ext, QuickOpenMain, ITransferInputBox, Plugin } from '../common/plugin-api-rpc'; +import { QuickOpenExt, PLUGIN_RPC_CONTEXT as Ext, QuickOpenMain, TransferInputBox, Plugin } from '../common/plugin-api-rpc'; import { QuickPickOptions, QuickPickItem, InputBoxOptions, InputBox, QuickPick, QuickInput } from '@theia/plugin'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; import { RPCProtocol } from '../common/rpc-protocol'; @@ -22,7 +22,7 @@ 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'; -import { QuickInputTitleButtonHandle, ITransferQuickPick, PluginPackage } from '../common'; +import { QuickInputTitleButtonHandle, TransferQuickPick, PluginPackage } from '../common'; import URI from 'vscode-uri'; import * as path from 'path'; import { quickPickItemToPickOpenItem } from './type-converters'; @@ -102,7 +102,7 @@ export class QuickOpenExtImpl implements QuickOpenExt { return hookCancellationToken(token, promise); } - showCustomQuickPick(options: ITransferQuickPick): void { + showCustomQuickPick(options: TransferQuickPick): void { this.proxy.$showCustomQuickPick(options); } @@ -129,7 +129,7 @@ export class QuickOpenExtImpl implements QuickOpenExt { hide(): void { this.proxy.$hide(); } - showInputBox(options: ITransferInputBox): void { + showInputBox(options: TransferInputBox): void { this.validateInputHandler = options && options.validateInput; this.proxy.$showInputBox(options, typeof this.validateInputHandler === 'function'); } @@ -291,15 +291,15 @@ export class QuickInputExt implements QuickInput { this.update({ value }); } - show() { + show(): void { throw new Error('Method implementation must be provided by extenders'); } - dispose() { + dispose(): void { this.disposableCollection.dispose(); } - protected update(changed: object) { + protected update(changed: object): void { /** * The args are just going to be set when we call show for the first time. * We return early when its invisible to avoid race condition @@ -341,19 +341,19 @@ export class QuickInputExt implements QuickInput { } } - _fireAccept() { + _fireAccept(): void { this.onDidAcceptEmitter.fire(undefined); } - _fireChangedValue(changedValue: string) { + _fireChangedValue(changedValue: string): void { this.onDidChangeValueEmitter.fire(changedValue); } - _fireHide() { + _fireHide(): void { this.onDidHideEmitter.fire(undefined); } - _fireButtonTrigger(btn: QuickInputButton) { + _fireButtonTrigger(btn: QuickInputButton): void { this.onDidTriggerButtonEmitter.fire(btn); } @@ -550,11 +550,11 @@ export class QuickPickExt extends QuickInputExt impleme this.update({ placeholder }); } - _fireChangedSelection(selections: T[]) { + _fireChangedSelection(selections: T[]): void { this.onDidChangeSelectionEmitter.fire(selections); } - _fireChangedActiveItem(changedItems: QuickOpenItem[]) { + _fireChangedActiveItem(changedItems: QuickOpenItem[]): void { this.onDidChangeActiveEmitter.fire(changedItems as unknown as T[]); } @@ -567,6 +567,7 @@ export class QuickPickExt extends QuickInputExt impleme } show(): void { + this.visible = true; this.quickOpen.showCustomQuickPick({ quickInputIndex: this.quickInputIndex, title: this.title, diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index 1aaf4c9a8c97d..addd8e6d9f854 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -879,7 +879,7 @@ export function fromColorPresentation(colorPresentation: theia.ColorPresentation }; } -export function quickPickItemToPickOpenItem(items: Item[]) { +export function quickPickItemToPickOpenItem(items: Item[]): PickOpenItem[] { const pickItems: PickOpenItem[] = []; for (let handle = 0; handle < items.length; handle++) { const item = items[handle];