mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-11 23:40:34 -05:00
1323 lines
39 KiB
TypeScript
1323 lines
39 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as dom from '../../../base/browser/dom.js';
|
|
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
|
|
import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';
|
|
import { Button, IButtonStyles } from '../../../base/browser/ui/button/button.js';
|
|
import { CountBadge, ICountBadgeStyles } from '../../../base/browser/ui/countBadge/countBadge.js';
|
|
import { IHoverDelegate, IHoverDelegateOptions } from '../../../base/browser/ui/hover/hoverDelegate.js';
|
|
import { IInputBoxStyles } from '../../../base/browser/ui/inputbox/inputBox.js';
|
|
import { IKeybindingLabelStyles } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
|
|
import { IListStyles } from '../../../base/browser/ui/list/listWidget.js';
|
|
import { IProgressBarStyles, ProgressBar } from '../../../base/browser/ui/progressbar/progressbar.js';
|
|
import { IToggleStyles, Toggle } from '../../../base/browser/ui/toggle/toggle.js';
|
|
import { equals } from '../../../base/common/arrays.js';
|
|
import { TimeoutTimer } from '../../../base/common/async.js';
|
|
import { Codicon } from '../../../base/common/codicons.js';
|
|
import { Emitter, Event, EventBufferer } from '../../../base/common/event.js';
|
|
import { KeyCode } from '../../../base/common/keyCodes.js';
|
|
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
|
|
import { isIOS } from '../../../base/common/platform.js';
|
|
import Severity from '../../../base/common/severity.js';
|
|
import { ThemeIcon } from '../../../base/common/themables.js';
|
|
import './media/quickInput.css';
|
|
import { localize } from '../../../nls.js';
|
|
import { IInputBox, IKeyMods, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickInputToggle, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, IQuickPickWillAcceptEvent, IQuickWidget, ItemActivation, NO_KEY_MODS, QuickInputButtonLocation, QuickInputHideReason, QuickInputType, QuickPickFocus } from '../common/quickInput.js';
|
|
import { QuickInputBox } from './quickInputBox.js';
|
|
import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils.js';
|
|
import { IConfigurationService } from '../../configuration/common/configuration.js';
|
|
import { IHoverService, WorkbenchHoverDelegate } from '../../hover/browser/hover.js';
|
|
import { QuickInputTree } from './quickInputTree.js';
|
|
import type { IHoverOptions } from '../../../base/browser/ui/hover/hover.js';
|
|
import { ContextKeyExpr, RawContextKey } from '../../contextkey/common/contextkey.js';
|
|
|
|
export const inQuickInputContextKeyValue = 'inQuickInput';
|
|
export const InQuickInputContextKey = new RawContextKey<boolean>(inQuickInputContextKeyValue, false, localize('inQuickInput', "Whether keyboard focus is inside the quick input control"));
|
|
export const inQuickInputContext = ContextKeyExpr.has(inQuickInputContextKeyValue);
|
|
|
|
export const quickInputAlignmentContextKeyValue = 'quickInputAlignment';
|
|
export const QuickInputAlignmentContextKey = new RawContextKey<'top' | 'center' | undefined>(quickInputAlignmentContextKeyValue, 'top', localize('quickInputAlignment', "The alignment of the quick input"));
|
|
|
|
export const quickInputTypeContextKeyValue = 'quickInputType';
|
|
export const QuickInputTypeContextKey = new RawContextKey<QuickInputType>(quickInputTypeContextKeyValue, undefined, localize('quickInputType', "The type of the currently visible quick input"));
|
|
|
|
export const endOfQuickInputBoxContextKeyValue = 'cursorAtEndOfQuickInputBox';
|
|
export const EndOfQuickInputBoxContextKey = new RawContextKey<boolean>(endOfQuickInputBoxContextKeyValue, false, localize('cursorAtEndOfQuickInputBox', "Whether the cursor in the quick input is at the end of the input box"));
|
|
export const endOfQuickInputBoxContext = ContextKeyExpr.has(endOfQuickInputBoxContextKeyValue);
|
|
|
|
export interface IQuickInputOptions {
|
|
idPrefix: string;
|
|
container: HTMLElement;
|
|
ignoreFocusOut(): boolean;
|
|
backKeybindingLabel(): string | undefined;
|
|
setContextKey(id?: string): void;
|
|
linkOpenerDelegate(content: string): void;
|
|
returnFocus(): void;
|
|
/**
|
|
* @todo With IHover in vs/editor, can we depend on the service directly
|
|
* instead of passing it through a hover delegate?
|
|
*/
|
|
hoverDelegate: IHoverDelegate;
|
|
styles: IQuickInputStyles;
|
|
}
|
|
|
|
export interface IQuickInputStyles {
|
|
readonly widget: IQuickInputWidgetStyles;
|
|
readonly inputBox: IInputBoxStyles;
|
|
readonly toggle: IToggleStyles;
|
|
readonly countBadge: ICountBadgeStyles;
|
|
readonly button: IButtonStyles;
|
|
readonly progressBar: IProgressBarStyles;
|
|
readonly keybindingLabel: IKeybindingLabelStyles;
|
|
readonly list: IListStyles;
|
|
readonly pickerGroup: { pickerGroupBorder: string | undefined; pickerGroupForeground: string | undefined };
|
|
}
|
|
|
|
export interface IQuickInputWidgetStyles {
|
|
readonly quickInputBackground: string | undefined;
|
|
readonly quickInputForeground: string | undefined;
|
|
readonly quickInputTitleBackground: string | undefined;
|
|
readonly widgetBorder: string | undefined;
|
|
readonly widgetShadow: string | undefined;
|
|
}
|
|
|
|
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
|
|
|
export const backButton = {
|
|
iconClass: ThemeIcon.asClassName(Codicon.quickInputBack),
|
|
tooltip: localize('quickInput.back', "Back"),
|
|
handle: -1 // TODO
|
|
};
|
|
|
|
export interface QuickInputUI {
|
|
container: HTMLElement;
|
|
styleSheet: HTMLStyleElement;
|
|
leftActionBar: ActionBar;
|
|
titleBar: HTMLElement;
|
|
title: HTMLElement;
|
|
description1: HTMLElement;
|
|
description2: HTMLElement;
|
|
widget: HTMLElement;
|
|
rightActionBar: ActionBar;
|
|
inlineActionBar: ActionBar;
|
|
checkAll: HTMLInputElement;
|
|
inputContainer: HTMLElement;
|
|
filterContainer: HTMLElement;
|
|
inputBox: QuickInputBox;
|
|
visibleCountContainer: HTMLElement;
|
|
visibleCount: CountBadge;
|
|
countContainer: HTMLElement;
|
|
count: CountBadge;
|
|
okContainer: HTMLElement;
|
|
ok: Button;
|
|
message: HTMLElement;
|
|
customButtonContainer: HTMLElement;
|
|
customButton: Button;
|
|
progressBar: ProgressBar;
|
|
list: QuickInputTree;
|
|
onDidAccept: Event<void>;
|
|
onDidCustom: Event<void>;
|
|
onDidTriggerButton: Event<IQuickInputButton>;
|
|
ignoreFocusOut: boolean;
|
|
keyMods: Writeable<IKeyMods>;
|
|
show(controller: QuickInput): void;
|
|
setVisibilities(visibilities: Visibilities): void;
|
|
setEnabled(enabled: boolean): void;
|
|
setContextKey(contextKey?: string): void;
|
|
linkOpenerDelegate(content: string): void;
|
|
hide(): void;
|
|
}
|
|
|
|
export type Visibilities = {
|
|
title?: boolean;
|
|
description?: boolean;
|
|
checkAll?: boolean;
|
|
inputBox?: boolean;
|
|
checkBox?: boolean;
|
|
visibleCount?: boolean;
|
|
count?: boolean;
|
|
message?: boolean;
|
|
list?: boolean;
|
|
ok?: boolean;
|
|
customButton?: boolean;
|
|
progressBar?: boolean;
|
|
};
|
|
|
|
abstract class QuickInput extends Disposable implements IQuickInput {
|
|
protected static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
|
|
|
|
private _title: string | undefined;
|
|
private _description: string | undefined;
|
|
private _widget: HTMLElement | undefined;
|
|
private _widgetUpdated = false;
|
|
private _steps: number | undefined;
|
|
private _totalSteps: number | undefined;
|
|
protected visible = false;
|
|
private _enabled = true;
|
|
private _contextKey: string | undefined;
|
|
private _busy = false;
|
|
private _ignoreFocusOut = false;
|
|
private _leftButtons: IQuickInputButton[] = [];
|
|
private _rightButtons: IQuickInputButton[] = [];
|
|
private _inlineButtons: IQuickInputButton[] = [];
|
|
private buttonsUpdated = false;
|
|
private _toggles: IQuickInputToggle[] = [];
|
|
private togglesUpdated = false;
|
|
protected noValidationMessage = QuickInput.noPromptMessage;
|
|
private _validationMessage: string | undefined;
|
|
private _lastValidationMessage: string | undefined;
|
|
private _severity: Severity = Severity.Ignore;
|
|
private _lastSeverity: Severity | undefined;
|
|
private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
|
|
private readonly onDidHideEmitter = this._register(new Emitter<IQuickInputHideEvent>());
|
|
private readonly onWillHideEmitter = this._register(new Emitter<IQuickInputHideEvent>());
|
|
private readonly onDisposeEmitter = this._register(new Emitter<void>());
|
|
|
|
protected readonly visibleDisposables = this._register(new DisposableStore());
|
|
|
|
private busyDelay: TimeoutTimer | undefined;
|
|
|
|
abstract type: QuickInputType;
|
|
|
|
constructor(
|
|
protected ui: QuickInputUI
|
|
) {
|
|
super();
|
|
}
|
|
|
|
get title() {
|
|
return this._title;
|
|
}
|
|
|
|
set title(title: string | undefined) {
|
|
this._title = title;
|
|
this.update();
|
|
}
|
|
|
|
get description() {
|
|
return this._description;
|
|
}
|
|
|
|
set description(description: string | undefined) {
|
|
this._description = description;
|
|
this.update();
|
|
}
|
|
|
|
get widget() {
|
|
return this._widget;
|
|
}
|
|
|
|
set widget(widget: unknown | undefined) {
|
|
if (!(dom.isHTMLElement(widget))) {
|
|
return;
|
|
}
|
|
if (this._widget !== widget) {
|
|
this._widget = widget;
|
|
this._widgetUpdated = true;
|
|
this.update();
|
|
}
|
|
}
|
|
|
|
get step() {
|
|
return this._steps;
|
|
}
|
|
|
|
set step(step: number | undefined) {
|
|
this._steps = step;
|
|
this.update();
|
|
}
|
|
|
|
get totalSteps() {
|
|
return this._totalSteps;
|
|
}
|
|
|
|
set totalSteps(totalSteps: number | undefined) {
|
|
this._totalSteps = totalSteps;
|
|
this.update();
|
|
}
|
|
|
|
get enabled() {
|
|
return this._enabled;
|
|
}
|
|
|
|
set enabled(enabled: boolean) {
|
|
this._enabled = enabled;
|
|
this.update();
|
|
}
|
|
|
|
get contextKey() {
|
|
return this._contextKey;
|
|
}
|
|
|
|
set contextKey(contextKey: string | undefined) {
|
|
this._contextKey = contextKey;
|
|
this.update();
|
|
}
|
|
|
|
get busy() {
|
|
return this._busy;
|
|
}
|
|
|
|
set busy(busy: boolean) {
|
|
this._busy = busy;
|
|
this.update();
|
|
}
|
|
|
|
get ignoreFocusOut() {
|
|
return this._ignoreFocusOut;
|
|
}
|
|
|
|
set ignoreFocusOut(ignoreFocusOut: boolean) {
|
|
const shouldUpdate = this._ignoreFocusOut !== ignoreFocusOut && !isIOS;
|
|
this._ignoreFocusOut = ignoreFocusOut && !isIOS;
|
|
if (shouldUpdate) {
|
|
this.update();
|
|
}
|
|
}
|
|
|
|
protected get titleButtons() {
|
|
return this._leftButtons.length
|
|
? [...this._leftButtons, this._rightButtons]
|
|
: this._rightButtons;
|
|
}
|
|
|
|
get buttons() {
|
|
return [
|
|
...this._leftButtons,
|
|
...this._rightButtons,
|
|
...this._inlineButtons
|
|
];
|
|
}
|
|
|
|
set buttons(buttons: IQuickInputButton[]) {
|
|
this._leftButtons = buttons.filter(b => b === backButton);
|
|
this._rightButtons = buttons.filter(b => b !== backButton && b.location !== QuickInputButtonLocation.Inline);
|
|
this._inlineButtons = buttons.filter(b => b.location === QuickInputButtonLocation.Inline);
|
|
this.buttonsUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
get toggles() {
|
|
return this._toggles;
|
|
}
|
|
|
|
set toggles(toggles: IQuickInputToggle[]) {
|
|
this._toggles = toggles ?? [];
|
|
this.togglesUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
get validationMessage() {
|
|
return this._validationMessage;
|
|
}
|
|
|
|
set validationMessage(validationMessage: string | undefined) {
|
|
this._validationMessage = validationMessage;
|
|
this.update();
|
|
}
|
|
|
|
get severity() {
|
|
return this._severity;
|
|
}
|
|
|
|
set severity(severity: Severity) {
|
|
this._severity = severity;
|
|
this.update();
|
|
}
|
|
|
|
readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event;
|
|
|
|
show(): void {
|
|
if (this.visible) {
|
|
return;
|
|
}
|
|
this.visibleDisposables.add(
|
|
this.ui.onDidTriggerButton(button => {
|
|
if (this.buttons.indexOf(button) !== -1) {
|
|
this.onDidTriggerButtonEmitter.fire(button);
|
|
}
|
|
}),
|
|
);
|
|
this.ui.show(this);
|
|
|
|
// update properties in the controller that get reset in the ui.show() call
|
|
this.visible = true;
|
|
// This ensures the message/prompt gets rendered
|
|
this._lastValidationMessage = undefined;
|
|
// This ensures the input box has the right severity applied
|
|
this._lastSeverity = undefined;
|
|
if (this.buttons.length) {
|
|
// if there are buttons, the ui.show() clears them out of the UI so we should
|
|
// rerender them.
|
|
this.buttonsUpdated = true;
|
|
}
|
|
if (this.toggles.length) {
|
|
// if there are toggles, the ui.show() clears them out of the UI so we should
|
|
// rerender them.
|
|
this.togglesUpdated = true;
|
|
}
|
|
|
|
this.update();
|
|
}
|
|
|
|
hide(): void {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
this.ui.hide();
|
|
}
|
|
|
|
didHide(reason = QuickInputHideReason.Other): void {
|
|
this.visible = false;
|
|
this.visibleDisposables.clear();
|
|
this.onDidHideEmitter.fire({ reason });
|
|
}
|
|
|
|
readonly onDidHide = this.onDidHideEmitter.event;
|
|
|
|
willHide(reason = QuickInputHideReason.Other): void {
|
|
this.onWillHideEmitter.fire({ reason });
|
|
}
|
|
readonly onWillHide = this.onWillHideEmitter.event;
|
|
|
|
protected update() {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
const title = this.getTitle();
|
|
if (title && this.ui.title.textContent !== title) {
|
|
this.ui.title.textContent = title;
|
|
} else if (!title && this.ui.title.innerHTML !== ' ') {
|
|
this.ui.title.innerText = '\u00a0';
|
|
}
|
|
const description = this.getDescription();
|
|
if (this.ui.description1.textContent !== description) {
|
|
this.ui.description1.textContent = description;
|
|
}
|
|
if (this.ui.description2.textContent !== description) {
|
|
this.ui.description2.textContent = description;
|
|
}
|
|
if (this._widgetUpdated) {
|
|
this._widgetUpdated = false;
|
|
if (this._widget) {
|
|
dom.reset(this.ui.widget, this._widget);
|
|
} else {
|
|
dom.reset(this.ui.widget);
|
|
}
|
|
}
|
|
if (this.busy && !this.busyDelay) {
|
|
this.busyDelay = new TimeoutTimer();
|
|
this.busyDelay.setIfNotSet(() => {
|
|
if (this.visible) {
|
|
this.ui.progressBar.infinite();
|
|
}
|
|
}, 800);
|
|
}
|
|
if (!this.busy && this.busyDelay) {
|
|
this.ui.progressBar.stop();
|
|
this.busyDelay.cancel();
|
|
this.busyDelay = undefined;
|
|
}
|
|
if (this.buttonsUpdated) {
|
|
this.buttonsUpdated = false;
|
|
this.ui.leftActionBar.clear();
|
|
const leftButtons = this._leftButtons
|
|
.map((button, index) => quickInputButtonToAction(
|
|
button,
|
|
`id-${index}`,
|
|
async () => this.onDidTriggerButtonEmitter.fire(button)
|
|
));
|
|
this.ui.leftActionBar.push(leftButtons, { icon: true, label: false });
|
|
this.ui.rightActionBar.clear();
|
|
const rightButtons = this._rightButtons
|
|
.map((button, index) => quickInputButtonToAction(
|
|
button,
|
|
`id-${index}`,
|
|
async () => this.onDidTriggerButtonEmitter.fire(button)
|
|
));
|
|
this.ui.rightActionBar.push(rightButtons, { icon: true, label: false });
|
|
this.ui.inlineActionBar.clear();
|
|
const inlineButtons = this._inlineButtons
|
|
.map((button, index) => quickInputButtonToAction(
|
|
button,
|
|
`id-${index}`,
|
|
async () => this.onDidTriggerButtonEmitter.fire(button)
|
|
));
|
|
this.ui.inlineActionBar.push(inlineButtons, { icon: true, label: false });
|
|
}
|
|
if (this.togglesUpdated) {
|
|
this.togglesUpdated = false;
|
|
// HACK: Filter out toggles here that are not concrete Toggle objects. This is to workaround
|
|
// a layering issue as quick input's interface is in common but Toggle is in browser and
|
|
// it requires a HTMLElement on its interface
|
|
const concreteToggles = this.toggles?.filter(opts => opts instanceof Toggle) as Toggle[] ?? [];
|
|
this.ui.inputBox.toggles = concreteToggles;
|
|
}
|
|
this.ui.ignoreFocusOut = this.ignoreFocusOut;
|
|
this.ui.setEnabled(this.enabled);
|
|
this.ui.setContextKey(this.contextKey);
|
|
|
|
const validationMessage = this.validationMessage || this.noValidationMessage;
|
|
if (this._lastValidationMessage !== validationMessage) {
|
|
this._lastValidationMessage = validationMessage;
|
|
dom.reset(this.ui.message);
|
|
renderQuickInputDescription(validationMessage, this.ui.message, {
|
|
callback: (content) => {
|
|
this.ui.linkOpenerDelegate(content);
|
|
},
|
|
disposables: this.visibleDisposables,
|
|
});
|
|
}
|
|
if (this._lastSeverity !== this.severity) {
|
|
this._lastSeverity = this.severity;
|
|
this.showMessageDecoration(this.severity);
|
|
}
|
|
}
|
|
|
|
private getTitle() {
|
|
if (this.title && this.step) {
|
|
return `${this.title} (${this.getSteps()})`;
|
|
}
|
|
if (this.title) {
|
|
return this.title;
|
|
}
|
|
if (this.step) {
|
|
return this.getSteps();
|
|
}
|
|
return '';
|
|
}
|
|
|
|
private getDescription() {
|
|
return this.description || '';
|
|
}
|
|
|
|
private getSteps() {
|
|
if (this.step && this.totalSteps) {
|
|
return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps);
|
|
}
|
|
if (this.step) {
|
|
return String(this.step);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
protected showMessageDecoration(severity: Severity) {
|
|
this.ui.inputBox.showDecoration(severity);
|
|
if (severity !== Severity.Ignore) {
|
|
const styles = this.ui.inputBox.stylesForType(severity);
|
|
this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : '';
|
|
this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';
|
|
this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : '';
|
|
this.ui.message.style.marginBottom = '-2px';
|
|
} else {
|
|
this.ui.message.style.color = '';
|
|
this.ui.message.style.backgroundColor = '';
|
|
this.ui.message.style.border = '';
|
|
this.ui.message.style.marginBottom = '';
|
|
}
|
|
}
|
|
|
|
readonly onDispose = this.onDisposeEmitter.event;
|
|
|
|
override dispose(): void {
|
|
this.hide();
|
|
this.onDisposeEmitter.fire();
|
|
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
export class QuickPick<T extends IQuickPickItem, O extends { useSeparators: boolean } = { useSeparators: false }> extends QuickInput implements IQuickPick<T, O> {
|
|
|
|
private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
|
|
|
|
private _value = '';
|
|
private _ariaLabel: string | undefined;
|
|
private _placeholder: string | undefined;
|
|
private readonly onDidChangeValueEmitter = this._register(new Emitter<string>());
|
|
private readonly onWillAcceptEmitter = this._register(new Emitter<IQuickPickWillAcceptEvent>());
|
|
private readonly onDidAcceptEmitter = this._register(new Emitter<IQuickPickDidAcceptEvent>());
|
|
private readonly onDidCustomEmitter = this._register(new Emitter<void>());
|
|
private _items: O extends { useSeparators: true } ? Array<T | IQuickPickSeparator> : Array<T> = [];
|
|
private itemsUpdated = false;
|
|
private _canSelectMany = false;
|
|
private _canAcceptInBackground = false;
|
|
private _matchOnDescription = false;
|
|
private _matchOnDetail = false;
|
|
private _matchOnLabel = true;
|
|
private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
|
|
private _sortByLabel = true;
|
|
private _keepScrollPosition = false;
|
|
private _itemActivation = ItemActivation.FIRST;
|
|
private _activeItems: T[] = [];
|
|
private activeItemsUpdated = false;
|
|
private activeItemsToConfirm: T[] | null = [];
|
|
private readonly onDidChangeActiveEmitter = this._register(new Emitter<T[]>());
|
|
private _selectedItems: T[] = [];
|
|
private selectedItemsUpdated = false;
|
|
private selectedItemsToConfirm: T[] | null = [];
|
|
private readonly onDidChangeSelectionEmitter = this._register(new Emitter<T[]>());
|
|
private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter<IQuickPickItemButtonEvent<T>>());
|
|
private readonly onDidTriggerSeparatorButtonEmitter = this._register(new Emitter<IQuickPickSeparatorButtonEvent>());
|
|
private _valueSelection: Readonly<[number, number]> | undefined;
|
|
private valueSelectionUpdated = true;
|
|
private _ok: boolean | 'default' = 'default';
|
|
private _okLabel: string | undefined;
|
|
private _customButton = false;
|
|
private _customButtonLabel: string | undefined;
|
|
private _customButtonHover: string | undefined;
|
|
private _quickNavigate: IQuickNavigateConfiguration | undefined;
|
|
private _hideInput: boolean | undefined;
|
|
private _hideCountBadge: boolean | undefined;
|
|
private _hideCheckAll: boolean | undefined;
|
|
private _focusEventBufferer = new EventBufferer();
|
|
|
|
readonly type = QuickInputType.QuickPick;
|
|
|
|
get quickNavigate() {
|
|
return this._quickNavigate;
|
|
}
|
|
|
|
set quickNavigate(quickNavigate: IQuickNavigateConfiguration | undefined) {
|
|
this._quickNavigate = quickNavigate;
|
|
this.update();
|
|
}
|
|
|
|
get value() {
|
|
return this._value;
|
|
}
|
|
|
|
set value(value: string) {
|
|
this.doSetValue(value);
|
|
}
|
|
|
|
private doSetValue(value: string, skipUpdate?: boolean): void {
|
|
if (this._value !== value) {
|
|
this._value = value;
|
|
if (!skipUpdate) {
|
|
this.update();
|
|
}
|
|
if (this.visible) {
|
|
const didFilter = this.ui.list.filter(this.filterValue(this._value));
|
|
if (didFilter) {
|
|
this.trySelectFirst();
|
|
}
|
|
}
|
|
this.onDidChangeValueEmitter.fire(this._value);
|
|
}
|
|
}
|
|
|
|
filterValue = (value: string) => value;
|
|
|
|
set ariaLabel(ariaLabel: string | undefined) {
|
|
this._ariaLabel = ariaLabel;
|
|
this.update();
|
|
}
|
|
|
|
get ariaLabel() {
|
|
return this._ariaLabel;
|
|
}
|
|
|
|
get placeholder() {
|
|
return this._placeholder;
|
|
}
|
|
|
|
set placeholder(placeholder: string | undefined) {
|
|
this._placeholder = placeholder;
|
|
this.update();
|
|
}
|
|
|
|
onDidChangeValue = this.onDidChangeValueEmitter.event;
|
|
|
|
onWillAccept = this.onWillAcceptEmitter.event;
|
|
onDidAccept = this.onDidAcceptEmitter.event;
|
|
|
|
onDidCustom = this.onDidCustomEmitter.event;
|
|
|
|
get items() {
|
|
return this._items;
|
|
}
|
|
|
|
get scrollTop() {
|
|
return this.ui.list.scrollTop;
|
|
}
|
|
|
|
private set scrollTop(scrollTop: number) {
|
|
this.ui.list.scrollTop = scrollTop;
|
|
}
|
|
|
|
set items(items: O extends { useSeparators: true } ? Array<T | IQuickPickSeparator> : Array<T>) {
|
|
this._items = items;
|
|
this.itemsUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
get canSelectMany() {
|
|
return this._canSelectMany;
|
|
}
|
|
|
|
set canSelectMany(canSelectMany: boolean) {
|
|
this._canSelectMany = canSelectMany;
|
|
this.update();
|
|
}
|
|
|
|
get canAcceptInBackground() {
|
|
return this._canAcceptInBackground;
|
|
}
|
|
|
|
set canAcceptInBackground(canAcceptInBackground: boolean) {
|
|
this._canAcceptInBackground = canAcceptInBackground;
|
|
}
|
|
|
|
get matchOnDescription() {
|
|
return this._matchOnDescription;
|
|
}
|
|
|
|
set matchOnDescription(matchOnDescription: boolean) {
|
|
this._matchOnDescription = matchOnDescription;
|
|
this.update();
|
|
}
|
|
|
|
get matchOnDetail() {
|
|
return this._matchOnDetail;
|
|
}
|
|
|
|
set matchOnDetail(matchOnDetail: boolean) {
|
|
this._matchOnDetail = matchOnDetail;
|
|
this.update();
|
|
}
|
|
|
|
get matchOnLabel() {
|
|
return this._matchOnLabel;
|
|
}
|
|
|
|
set matchOnLabel(matchOnLabel: boolean) {
|
|
this._matchOnLabel = matchOnLabel;
|
|
this.update();
|
|
}
|
|
|
|
get matchOnLabelMode() {
|
|
return this._matchOnLabelMode;
|
|
}
|
|
|
|
set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') {
|
|
this._matchOnLabelMode = matchOnLabelMode;
|
|
this.update();
|
|
}
|
|
|
|
get sortByLabel() {
|
|
return this._sortByLabel;
|
|
}
|
|
|
|
set sortByLabel(sortByLabel: boolean) {
|
|
this._sortByLabel = sortByLabel;
|
|
this.update();
|
|
}
|
|
|
|
get keepScrollPosition() {
|
|
return this._keepScrollPosition;
|
|
}
|
|
|
|
set keepScrollPosition(keepScrollPosition: boolean) {
|
|
this._keepScrollPosition = keepScrollPosition;
|
|
}
|
|
|
|
get itemActivation() {
|
|
return this._itemActivation;
|
|
}
|
|
|
|
set itemActivation(itemActivation: ItemActivation) {
|
|
this._itemActivation = itemActivation;
|
|
}
|
|
|
|
get activeItems() {
|
|
return this._activeItems;
|
|
}
|
|
|
|
set activeItems(activeItems: T[]) {
|
|
this._activeItems = activeItems;
|
|
this.activeItemsUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
onDidChangeActive = this.onDidChangeActiveEmitter.event;
|
|
|
|
get selectedItems() {
|
|
return this._selectedItems;
|
|
}
|
|
|
|
set selectedItems(selectedItems: T[]) {
|
|
this._selectedItems = selectedItems;
|
|
this.selectedItemsUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
get keyMods() {
|
|
if (this._quickNavigate) {
|
|
// Disable keyMods when quick navigate is enabled
|
|
// because in this model the interaction is purely
|
|
// keyboard driven and Ctrl/Alt are typically
|
|
// pressed and hold during this interaction.
|
|
return NO_KEY_MODS;
|
|
}
|
|
return this.ui.keyMods;
|
|
}
|
|
|
|
get valueSelection() {
|
|
const selection = this.ui.inputBox.getSelection();
|
|
if (!selection) {
|
|
return undefined;
|
|
}
|
|
return [selection.start, selection.end];
|
|
}
|
|
|
|
set valueSelection(valueSelection: Readonly<[number, number]> | undefined) {
|
|
this._valueSelection = valueSelection;
|
|
this.valueSelectionUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
get customButton() {
|
|
return this._customButton;
|
|
}
|
|
|
|
set customButton(showCustomButton: boolean) {
|
|
this._customButton = showCustomButton;
|
|
this.update();
|
|
}
|
|
|
|
get customLabel() {
|
|
return this._customButtonLabel;
|
|
}
|
|
|
|
set customLabel(label: string | undefined) {
|
|
this._customButtonLabel = label;
|
|
this.update();
|
|
}
|
|
|
|
get customHover() {
|
|
return this._customButtonHover;
|
|
}
|
|
|
|
set customHover(hover: string | undefined) {
|
|
this._customButtonHover = hover;
|
|
this.update();
|
|
}
|
|
|
|
get ok() {
|
|
return this._ok;
|
|
}
|
|
|
|
set ok(showOkButton: boolean | 'default') {
|
|
this._ok = showOkButton;
|
|
this.update();
|
|
}
|
|
|
|
get okLabel() {
|
|
return this._okLabel ?? localize('ok', "OK");
|
|
}
|
|
|
|
set okLabel(okLabel: string | undefined) {
|
|
this._okLabel = okLabel;
|
|
this.update();
|
|
}
|
|
|
|
inputHasFocus(): boolean {
|
|
return this.visible ? this.ui.inputBox.hasFocus() : false;
|
|
}
|
|
|
|
focusOnInput() {
|
|
this.ui.inputBox.setFocus();
|
|
}
|
|
|
|
get hideInput() {
|
|
return !!this._hideInput;
|
|
}
|
|
|
|
set hideInput(hideInput: boolean) {
|
|
this._hideInput = hideInput;
|
|
this.update();
|
|
}
|
|
|
|
get hideCountBadge() {
|
|
return !!this._hideCountBadge;
|
|
}
|
|
|
|
set hideCountBadge(hideCountBadge: boolean) {
|
|
this._hideCountBadge = hideCountBadge;
|
|
this.update();
|
|
}
|
|
|
|
get hideCheckAll() {
|
|
return !!this._hideCheckAll;
|
|
}
|
|
|
|
set hideCheckAll(hideCheckAll: boolean) {
|
|
this._hideCheckAll = hideCheckAll;
|
|
this.update();
|
|
}
|
|
|
|
onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
|
|
|
|
onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
|
|
|
|
onDidTriggerSeparatorButton = this.onDidTriggerSeparatorButtonEmitter.event;
|
|
|
|
private trySelectFirst() {
|
|
if (!this.canSelectMany) {
|
|
this.ui.list.focus(QuickPickFocus.First);
|
|
}
|
|
}
|
|
|
|
override show() {
|
|
if (!this.visible) {
|
|
this.visibleDisposables.add(
|
|
this.ui.inputBox.onDidChange(value => {
|
|
this.doSetValue(value, true /* skip update since this originates from the UI */);
|
|
}));
|
|
this.visibleDisposables.add(this.ui.onDidAccept(() => {
|
|
if (this.canSelectMany) {
|
|
// if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite
|
|
// `_selectedItems`. In that case, we should emit one with an empty array to ensure that
|
|
// `.selectedItems` is up to date.
|
|
if (!this.ui.list.getCheckedElements().length) {
|
|
this._selectedItems = [];
|
|
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
|
}
|
|
} else if (this.activeItems[0]) {
|
|
// For single-select, we set `selectedItems` to the item that was accepted.
|
|
this._selectedItems = [this.activeItems[0]];
|
|
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
|
}
|
|
this.handleAccept(false);
|
|
}));
|
|
this.visibleDisposables.add(this.ui.onDidCustom(() => {
|
|
this.onDidCustomEmitter.fire();
|
|
}));
|
|
this.visibleDisposables.add(this._focusEventBufferer.wrapEvent(
|
|
this.ui.list.onDidChangeFocus,
|
|
// Only fire the last event
|
|
(_, e) => e
|
|
)(focusedItems => {
|
|
if (this.activeItemsUpdated) {
|
|
return; // Expect another event.
|
|
}
|
|
if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {
|
|
return;
|
|
}
|
|
this._activeItems = focusedItems as T[];
|
|
this.onDidChangeActiveEmitter.fire(focusedItems as T[]);
|
|
}));
|
|
this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => {
|
|
if (this.canSelectMany && !selectedItems.some(i => i.pickable === false)) {
|
|
if (selectedItems.length) {
|
|
this.ui.list.setSelectedElements([]);
|
|
}
|
|
return;
|
|
}
|
|
if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
|
|
return;
|
|
}
|
|
this._selectedItems = selectedItems as T[];
|
|
this.onDidChangeSelectionEmitter.fire(selectedItems as T[]);
|
|
if (selectedItems.length) {
|
|
this.handleAccept(dom.isMouseEvent(event) && event.button === 1 /* mouse middle click */);
|
|
}
|
|
}));
|
|
this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => {
|
|
if (!this.canSelectMany || !this.visible) {
|
|
return;
|
|
}
|
|
if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {
|
|
return;
|
|
}
|
|
this._selectedItems = checkedItems as T[];
|
|
this.onDidChangeSelectionEmitter.fire(checkedItems as T[]);
|
|
}));
|
|
this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>)));
|
|
this.visibleDisposables.add(this.ui.list.onSeparatorButtonTriggered(event => this.onDidTriggerSeparatorButtonEmitter.fire(event)));
|
|
this.visibleDisposables.add(this.registerQuickNavigation());
|
|
this.valueSelectionUpdated = true;
|
|
}
|
|
super.show(); // TODO: Why have show() bubble up while update() trickles down?
|
|
}
|
|
|
|
private handleAccept(inBackground: boolean): void {
|
|
|
|
// Figure out veto via `onWillAccept` event
|
|
let veto = false;
|
|
this.onWillAcceptEmitter.fire({ veto: () => veto = true });
|
|
|
|
// Continue with `onDidAccept` if no veto
|
|
if (!veto) {
|
|
this.onDidAcceptEmitter.fire({ inBackground });
|
|
}
|
|
}
|
|
|
|
private registerQuickNavigation() {
|
|
return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {
|
|
if (this.canSelectMany || !this._quickNavigate) {
|
|
return;
|
|
}
|
|
|
|
const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e);
|
|
const keyCode = keyboardEvent.keyCode;
|
|
|
|
// Select element when keys are pressed that signal it
|
|
const quickNavKeys = this._quickNavigate.keybindings;
|
|
const wasTriggerKeyPressed = quickNavKeys.some(k => {
|
|
const chords = k.getChords();
|
|
if (chords.length > 1) {
|
|
return false;
|
|
}
|
|
|
|
if (chords[0].shiftKey && keyCode === KeyCode.Shift) {
|
|
if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
|
|
return false; // this is an optimistic check for the shift key being used to navigate back in quick input
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (chords[0].altKey && keyCode === KeyCode.Alt) {
|
|
return true;
|
|
}
|
|
|
|
if (chords[0].ctrlKey && keyCode === KeyCode.Ctrl) {
|
|
return true;
|
|
}
|
|
|
|
if (chords[0].metaKey && keyCode === KeyCode.Meta) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (wasTriggerKeyPressed) {
|
|
if (this.activeItems[0]) {
|
|
this._selectedItems = [this.activeItems[0]];
|
|
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
|
this.handleAccept(false);
|
|
}
|
|
// Unset quick navigate after press. It is only valid once
|
|
// and should not result in any behaviour change afterwards
|
|
// if the picker remains open because there was no active item
|
|
this._quickNavigate = undefined;
|
|
}
|
|
});
|
|
}
|
|
|
|
protected override update() {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
// store the scrollTop before it is reset
|
|
const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0;
|
|
const hasDescription = !!this.description;
|
|
const visibilities: Visibilities = {
|
|
title: !!this.title || !!this.step || !!this.titleButtons.length,
|
|
description: hasDescription,
|
|
checkAll: this.canSelectMany && !this._hideCheckAll,
|
|
checkBox: this.canSelectMany,
|
|
inputBox: !this._hideInput,
|
|
progressBar: !this._hideInput || hasDescription,
|
|
visibleCount: true,
|
|
count: this.canSelectMany && !this._hideCountBadge,
|
|
ok: this.ok === 'default' ? this.canSelectMany : this.ok,
|
|
list: true,
|
|
message: !!this.validationMessage,
|
|
customButton: this.customButton
|
|
};
|
|
this.ui.setVisibilities(visibilities);
|
|
super.update();
|
|
if (this.ui.inputBox.value !== this.value) {
|
|
this.ui.inputBox.value = this.value;
|
|
}
|
|
if (this.valueSelectionUpdated) {
|
|
this.valueSelectionUpdated = false;
|
|
this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
|
|
}
|
|
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
|
|
this.ui.inputBox.placeholder = (this.placeholder || '');
|
|
}
|
|
|
|
let ariaLabel = this.ariaLabel;
|
|
// Only set aria label to the input box placeholder if we actually have an input box.
|
|
if (!ariaLabel && visibilities.inputBox) {
|
|
ariaLabel = this.placeholder || QuickPick.DEFAULT_ARIA_LABEL;
|
|
// If we have a title, include it in the aria label.
|
|
if (this.title) {
|
|
ariaLabel += ` - ${this.title}`;
|
|
}
|
|
}
|
|
if (this.ui.list.ariaLabel !== ariaLabel) {
|
|
this.ui.list.ariaLabel = ariaLabel ?? null;
|
|
}
|
|
this.ui.list.matchOnDescription = this.matchOnDescription;
|
|
this.ui.list.matchOnDetail = this.matchOnDetail;
|
|
this.ui.list.matchOnLabel = this.matchOnLabel;
|
|
this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
|
|
this.ui.list.sortByLabel = this.sortByLabel;
|
|
if (this.itemsUpdated) {
|
|
this.itemsUpdated = false;
|
|
this._focusEventBufferer.bufferEvents(() => {
|
|
this.ui.list.setElements(this.items);
|
|
// We want focus to exist in the list if there are items so that space can be used to toggle
|
|
this.ui.list.shouldLoop = !this.canSelectMany;
|
|
this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
|
|
switch (this._itemActivation) {
|
|
case ItemActivation.NONE:
|
|
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
|
|
break;
|
|
case ItemActivation.SECOND:
|
|
this.ui.list.focus(QuickPickFocus.Second);
|
|
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
|
|
break;
|
|
case ItemActivation.LAST:
|
|
this.ui.list.focus(QuickPickFocus.Last);
|
|
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
|
|
break;
|
|
default:
|
|
this.trySelectFirst();
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
|
|
if (this.canSelectMany) {
|
|
this.ui.list.clearFocus();
|
|
} else {
|
|
this.trySelectFirst();
|
|
}
|
|
}
|
|
if (this.activeItemsUpdated) {
|
|
this.activeItemsUpdated = false;
|
|
this.activeItemsToConfirm = this._activeItems;
|
|
this.ui.list.setFocusedElements(this.activeItems);
|
|
if (this.activeItemsToConfirm === this._activeItems) {
|
|
this.activeItemsToConfirm = null;
|
|
}
|
|
}
|
|
if (this.selectedItemsUpdated) {
|
|
this.selectedItemsUpdated = false;
|
|
this.selectedItemsToConfirm = this._selectedItems;
|
|
if (this.canSelectMany) {
|
|
this.ui.list.setCheckedElements(this.selectedItems);
|
|
} else {
|
|
this.ui.list.setSelectedElements(this.selectedItems);
|
|
}
|
|
if (this.selectedItemsToConfirm === this._selectedItems) {
|
|
this.selectedItemsToConfirm = null;
|
|
}
|
|
}
|
|
this.ui.ok.label = this.okLabel || '';
|
|
this.ui.customButton.label = this.customLabel || '';
|
|
this.ui.customButton.element.title = this.customHover || '';
|
|
if (!visibilities.inputBox) {
|
|
// we need to move focus into the tree to detect keybindings
|
|
// properly when the input box is not visible (quick nav)
|
|
this.ui.list.domFocus();
|
|
|
|
// Focus the first element in the list if multiselect is enabled
|
|
if (this.canSelectMany) {
|
|
this.ui.list.focus(QuickPickFocus.First);
|
|
}
|
|
}
|
|
|
|
// Set the scroll position to what it was before updating the items
|
|
if (this.keepScrollPosition) {
|
|
this.scrollTop = scrollTopBefore;
|
|
}
|
|
}
|
|
|
|
focus(focus: QuickPickFocus): void {
|
|
this.ui.list.focus(focus);
|
|
// To allow things like space to check/uncheck items
|
|
if (this.canSelectMany) {
|
|
this.ui.list.domFocus();
|
|
}
|
|
}
|
|
|
|
accept(inBackground?: boolean | undefined): void {
|
|
if (inBackground && !this._canAcceptInBackground) {
|
|
return; // needs to be enabled
|
|
}
|
|
|
|
if (this.activeItems[0]) {
|
|
this._selectedItems = [this.activeItems[0]];
|
|
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
|
this.handleAccept(inBackground ?? false);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class InputBox extends QuickInput implements IInputBox {
|
|
private _value = '';
|
|
private _valueSelection: Readonly<[number, number]> | undefined;
|
|
private valueSelectionUpdated = true;
|
|
private _placeholder: string | undefined;
|
|
private _password = false;
|
|
private _prompt: string | undefined;
|
|
private readonly onDidValueChangeEmitter = this._register(new Emitter<string>());
|
|
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
|
|
|
|
readonly type = QuickInputType.InputBox;
|
|
|
|
get value() {
|
|
return this._value;
|
|
}
|
|
|
|
set value(value: string) {
|
|
this._value = value || '';
|
|
this.update();
|
|
}
|
|
|
|
get valueSelection() {
|
|
const selection = this.ui.inputBox.getSelection();
|
|
if (!selection) {
|
|
return undefined;
|
|
}
|
|
return [selection.start, selection.end];
|
|
}
|
|
|
|
set valueSelection(valueSelection: Readonly<[number, number]> | undefined) {
|
|
this._valueSelection = valueSelection;
|
|
this.valueSelectionUpdated = true;
|
|
this.update();
|
|
}
|
|
|
|
get placeholder() {
|
|
return this._placeholder;
|
|
}
|
|
|
|
set placeholder(placeholder: string | undefined) {
|
|
this._placeholder = placeholder;
|
|
this.update();
|
|
}
|
|
|
|
get password() {
|
|
return this._password;
|
|
}
|
|
|
|
set password(password: boolean) {
|
|
this._password = password;
|
|
this.update();
|
|
}
|
|
|
|
get prompt() {
|
|
return this._prompt;
|
|
}
|
|
|
|
set prompt(prompt: string | undefined) {
|
|
this._prompt = prompt;
|
|
this.noValidationMessage = prompt
|
|
? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt)
|
|
: QuickInput.noPromptMessage;
|
|
this.update();
|
|
}
|
|
|
|
readonly onDidChangeValue = this.onDidValueChangeEmitter.event;
|
|
|
|
readonly onDidAccept = this.onDidAcceptEmitter.event;
|
|
|
|
override show() {
|
|
if (!this.visible) {
|
|
this.visibleDisposables.add(
|
|
this.ui.inputBox.onDidChange(value => {
|
|
if (value === this.value) {
|
|
return;
|
|
}
|
|
this._value = value;
|
|
this.onDidValueChangeEmitter.fire(value);
|
|
}));
|
|
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()));
|
|
this.valueSelectionUpdated = true;
|
|
}
|
|
super.show();
|
|
}
|
|
|
|
protected override update() {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
|
|
this.ui.container.classList.remove('hidden-input');
|
|
const visibilities: Visibilities = {
|
|
title: !!this.title || !!this.step || !!this.titleButtons.length,
|
|
description: !!this.description || !!this.step,
|
|
inputBox: true,
|
|
message: true,
|
|
progressBar: true
|
|
};
|
|
|
|
this.ui.setVisibilities(visibilities);
|
|
super.update();
|
|
if (this.ui.inputBox.value !== this.value) {
|
|
this.ui.inputBox.value = this.value;
|
|
}
|
|
if (this.valueSelectionUpdated) {
|
|
this.valueSelectionUpdated = false;
|
|
this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
|
|
}
|
|
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
|
|
this.ui.inputBox.placeholder = (this.placeholder || '');
|
|
}
|
|
if (this.ui.inputBox.password !== this.password) {
|
|
this.ui.inputBox.password = this.password;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class QuickWidget extends QuickInput implements IQuickWidget {
|
|
readonly type = QuickInputType.QuickWidget;
|
|
|
|
protected override update() {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
|
|
const visibilities: Visibilities = {
|
|
title: !!this.title || !!this.step || !!this.titleButtons.length,
|
|
description: !!this.description || !!this.step
|
|
};
|
|
|
|
this.ui.setVisibilities(visibilities);
|
|
super.update();
|
|
}
|
|
}
|
|
|
|
export class QuickInputHoverDelegate extends WorkbenchHoverDelegate {
|
|
|
|
constructor(
|
|
@IConfigurationService configurationService: IConfigurationService,
|
|
@IHoverService hoverService: IHoverService
|
|
) {
|
|
super('element', undefined, (options) => this.getOverrideOptions(options), configurationService, hoverService);
|
|
}
|
|
|
|
private getOverrideOptions(options: IHoverDelegateOptions): Partial<IHoverOptions> {
|
|
// Only show the hover hint if the content is of a decent size
|
|
const showHoverHint = (
|
|
dom.isHTMLElement(options.content)
|
|
? options.content.textContent ?? ''
|
|
: typeof options.content === 'string'
|
|
? options.content
|
|
: options.content.value
|
|
).includes('\n');
|
|
|
|
return {
|
|
persistence: {
|
|
hideOnKeyDown: false,
|
|
},
|
|
appearance: {
|
|
showHoverHint,
|
|
skipFadeInAnimation: true,
|
|
},
|
|
};
|
|
}
|
|
}
|