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

Improve QuickInput/InputBox API's #5890

Merged
merged 3 commits into from
Aug 8, 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
2 changes: 2 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { ResourceContextKey } from './resource-context-key';
import { KeyboardLayoutService } from './keyboard/keyboard-layout-service';
import { MimeService } from './mime-service';
import { ApplicationShellMouseTracker } from './shell/application-shell-mouse-tracker';
import { QuickTitleBar } from './quick-open/quick-title-bar';

export const frontendApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => {
const themeService = ThemeService.get();
Expand Down Expand Up @@ -161,6 +162,7 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo

bind(QuickOpenService).toSelf().inSingletonScope();
bind(QuickInputService).toSelf().inSingletonScope();
bind(QuickTitleBar).toSelf().inSingletonScope();
bind(QuickCommandService).toSelf().inSingletonScope();
bind(QuickCommandFrontendContribution).toSelf().inSingletonScope();
[CommandContribution, KeybindingContribution, MenuContribution].forEach(serviceIdentifier =>
Expand Down
21 changes: 5 additions & 16 deletions packages/core/src/browser/keybinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Disposable, DisposableCollection } from '../../common/disposable';
import { ILogger } from '../../common/logger';
import { MaybePromise } from '../../common/types';
import { QuickOpenActionProvider } from './quick-open-action-provider';
import { QuickTitleBar } from './quick-title-bar';

export const QuickOpenContribution = Symbol('QuickOpenContribution');
/**
Expand Down Expand Up @@ -141,6 +142,9 @@ export class PrefixQuickOpenService {
@inject(QuickOpenService)
protected readonly quickOpenService: QuickOpenService;

@inject(QuickTitleBar)
protected readonly quickTitleBar: QuickTitleBar;

/**
* Opens a quick open widget with the model that handles the known prefixes.
* @param prefix string that may contain a prefix of some of the known quick open handlers.
Expand Down Expand Up @@ -188,6 +192,9 @@ export class PrefixQuickOpenService {
}

protected doOpen(options?: QuickOpenOptions): void {
if (this.quickTitleBar.isAttached) {
this.quickTitleBar.hide();
}
this.quickOpenService.open({
onType: (lookFor, acceptor) => this.onType(lookFor, acceptor)
}, options);
Expand Down
82 changes: 80 additions & 2 deletions packages/core/src/browser/quick-open/quick-input-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,47 @@ import { QuickOpenItem, QuickOpenMode } from './quick-open-model';
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 } from './quick-title-bar';
import { QuickTitleButton } from '../../common/quick-open-model';

export interface QuickInputOptions {

/**
* Show the progress indicator if true
*/
busy?: boolean

/**
* Allow user input
*/
enabled?: boolean;

/**
* Current step count
*/
step?: number | undefined

/**
* The title of the input
*/
title?: string | undefined

/**
* Total number of steps
*/
totalSteps?: number | undefined

/**
* Buttons that are displayed on the title panel
*/
buttons?: ReadonlyArray<QuickTitleButton>

/**
* Text for when there is a problem with the current input value
*/
validationMessage?: string | undefined;

/**
* The prefill value.
*/
Expand All @@ -47,6 +86,14 @@ export interface QuickInputOptions {
*/
ignoreFocusOut?: boolean;

/**
* Selection of the prefilled [`value`](#InputBoxOptions.value). Defined as tuple of two number where the
* first is the inclusive start index and the second the exclusive end index. When `undefined` the whole
* word will be selected, when empty (start equals end) only the cursor will be set,
* otherwise the defined range will be selected.
*/
valueSelection?: [number, number]

/**
* An optional function that will be called to validate input and to give a hint
* to the user.
Expand All @@ -63,15 +110,20 @@ export class QuickInputService {
@inject(QuickOpenService)
protected readonly quickOpenService: QuickOpenService;

@inject(QuickTitleBar)
protected readonly quickTitleBar: QuickTitleBar;

open(options: QuickInputOptions): Promise<string | undefined> {
const result = new Deferred<string | undefined>();
const prompt = this.createPrompt(options.prompt);
let label = prompt;
let currentText = '';
const validateInput = options && options.validateInput;

this.quickOpenService.open({
onType: async (lookFor, acceptor) => {
const error = validateInput ? await validateInput(lookFor) : undefined;
this.onDidChangeValueEmitter.fire(lookFor);
const error = validateInput && lookFor !== undefined ? await validateInput(lookFor) : undefined;
label = error || prompt;
if (error) {
this.quickOpenService.showDecoration(MessageType.Error);
Expand All @@ -83,6 +135,8 @@ export class QuickInputService {
run: mode => {
if (!error && mode === QuickOpenMode.OPEN) {
result.resolve(currentText);
this.onDidAcceptEmitter.fire(undefined);
this.quickTitleBar.hide();
return true;
}
return false;
Expand All @@ -95,14 +149,38 @@ export class QuickInputService {
placeholder: options.placeHolder,
password: options.password,
ignoreFocusOut: options.ignoreFocusOut,
onClose: () => result.resolve(undefined)
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);
}

return result.promise;
}

refresh(): void {
this.quickOpenService.refresh();
}

protected defaultPrompt = "Press 'Enter' to confirm your input or 'Escape' to cancel";
protected createPrompt(prompt?: string): string {
return prompt ? `${prompt} (${this.defaultPrompt})` : this.defaultPrompt;
}

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

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

}
38 changes: 17 additions & 21 deletions packages/core/src/browser/quick-open/quick-open-action-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<QuickOpenAction[]>;
}

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<void>;
}
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 {
Expand Down
120 changes: 26 additions & 94 deletions packages/core/src/browser/quick-open/quick-open-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends QuickOpenItemOptions = QuickOpenItemOptions> {

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<T extends QuickOpenGroupItemOptions = QuickOpenGroupItemOptions> extends QuickOpenItem<T> {

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;
Loading