Skip to content

Commit

Permalink
Improve QuickInput/InputBox API's. Partially fixes: #5109
Browse files Browse the repository at this point in the history
Co-authored-by: Josh Pinkney <[email protected]>
Co-authored-by: Igor Vinokur <[email protected]>

Signed-off-by: Josh Pinkney <[email protected]>
  • Loading branch information
JPinkney committed Jul 26, 2019
1 parent f5161db commit 1f2da76
Show file tree
Hide file tree
Showing 16 changed files with 1,410 additions and 108 deletions.
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 @@ -76,6 +76,7 @@ import { MimeService } from './mime-service';
import { ApplicationShellMouseTracker } from './shell/application-shell-mouse-tracker';
import { ViewContainer } from './view-container';
import { Widget } from './widgets';
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 @@ -163,6 +164,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
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
80 changes: 78 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,46 @@ 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, QuickInputTitleButton } from './quick-title-bar';

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<QuickInputTitleButton>

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

/**
* The prefill value.
*/
Expand All @@ -47,6 +85,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 +109,24 @@ 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;

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) => {
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 +138,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 +152,33 @@ 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();
}
});
return result.promise;
}

refresh() {
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;
}

}
6 changes: 6 additions & 0 deletions packages/core/src/browser/quick-open/quick-open-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ export class QuickOpenService {
hide(reason?: common.QuickOpenHideReason): void { }
showDecoration(type: MessageType): void { }
hideDecoration(): void { }
refresh(): void { }

/**
* Dom node of the QuickOpenWidget
*/
widgetNode: HTMLElement;
}
35 changes: 28 additions & 7 deletions packages/core/src/browser/quick-open/quick-pick-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ import { QuickOpenItem, QuickOpenMode, QuickOpenGroupItem, QuickOpenItemOptions
import { QuickOpenService } from './quick-open-service';
import { QuickPickService, QuickPickOptions, QuickPickItem, QuickPickSeparator, QuickPickValue } from '../../common/quick-pick-service';
import { QuickOpenHideReason } from '../../common/quick-open-service';
import { QuickTitleBar } from './quick-title-bar';
import { Emitter, Event } from '../../common/event';

@injectable()
export class QuickPickServiceImpl implements QuickPickService {

@inject(QuickTitleBar)
protected readonly quickTitleBar: QuickTitleBar;

@inject(QuickOpenService)
protected readonly quickOpenService: QuickOpenService;

Expand All @@ -31,18 +36,27 @@ export class QuickPickServiceImpl implements QuickPickService {
async show(elements: (string | QuickPickItem<Object>)[], options?: QuickPickOptions): Promise<Object | undefined> {
return new Promise<Object | undefined>(resolve => {
const items = this.toItems(elements, resolve);
if (items.length === 0) {
resolve(undefined);
return;
}
if (items.length === 1) {
items[0].run(QuickOpenMode.OPEN);
return;
}
this.quickOpenService.open({ onType: (_, acceptor) => acceptor(items) }, Object.assign({
onClose: () => resolve(undefined),
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 : '';
this.quickOpenService.open({
onType: (_, acceptor) => {
acceptor(items);
this.onDidChangeActiveItemsEmitter.fire(items);
}
}, Object.assign({
onClose: () => {
resolve(undefined);
this.quickTitleBar.hide();
},
fuzzyMatchLabel: true,
fuzzyMatchDescription: true
fuzzyMatchDescription: true,
prefix
}, options));
});
}
Expand Down Expand Up @@ -80,6 +94,7 @@ export class QuickPickServiceImpl implements QuickPickService {
return false;
}
resolve(value);
this.onDidAcceptEmitter.fire(undefined);
return true;
}
};
Expand All @@ -89,4 +104,10 @@ export class QuickPickServiceImpl implements QuickPickService {
this.quickOpenService.hide(reason);
}

private readonly onDidAcceptEmitter: Emitter<void> = new Emitter();
readonly onDidAccept: Event<void> = this.onDidAcceptEmitter.event;

private readonly onDidChangeActiveItemsEmitter: Emitter<QuickOpenItem<QuickOpenItemOptions>[]> = new Emitter<QuickOpenItem<QuickOpenItemOptions>[]>();
readonly onDidChangeActiveItems: Event<QuickOpenItem<QuickOpenItemOptions>[]> = this.onDidChangeActiveItemsEmitter.event;

}
Loading

0 comments on commit 1f2da76

Please sign in to comment.