Path: blob/main/extensions/copilot/test/simulation/fixtures/codeMapper/quickInput.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../base/browser/dom.js';6import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';7import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';8import { Button, IButtonStyles } from '../../../base/browser/ui/button/button.js';9import { CountBadge, ICountBadgeStyles } from '../../../base/browser/ui/countBadge/countBadge.js';10import { IHoverDelegate, IHoverDelegateOptions } from '../../../base/browser/ui/hover/hoverDelegate.js';11import { IInputBoxStyles } from '../../../base/browser/ui/inputbox/inputBox.js';12import { IKeybindingLabelStyles } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js';13import { IListStyles } from '../../../base/browser/ui/list/listWidget.js';14import { IProgressBarStyles, ProgressBar } from '../../../base/browser/ui/progressbar/progressbar.js';15import { IToggleStyles, Toggle } from '../../../base/browser/ui/toggle/toggle.js';16import { equals } from '../../../base/common/arrays.js';17import { TimeoutTimer } from '../../../base/common/async.js';18import { Codicon } from '../../../base/common/codicons.js';19import { Emitter, Event, EventBufferer } from '../../../base/common/event.js';20import { KeyCode } from '../../../base/common/keyCodes.js';21import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';22import { isIOS } from '../../../base/common/platform.js';23import Severity from '../../../base/common/severity.js';24import { ThemeIcon } from '../../../base/common/themables.js';25import './media/quickInput.css';26import { localize } from '../../../nls.js';27import { 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';28import { QuickInputBox } from './quickInputBox.js';29import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils.js';30import { IConfigurationService } from '../../configuration/common/configuration.js';31import { IHoverService, WorkbenchHoverDelegate } from '../../hover/browser/hover.js';32import { QuickInputTree } from './quickInputTree.js';33import type { IHoverOptions } from '../../../base/browser/ui/hover/hover.js';34import { ContextKeyExpr, RawContextKey } from '../../contextkey/common/contextkey.js';3536export const inQuickInputContextKeyValue = 'inQuickInput';37export const InQuickInputContextKey = new RawContextKey<boolean>(inQuickInputContextKeyValue, false, localize('inQuickInput', "Whether keyboard focus is inside the quick input control"));38export const inQuickInputContext = ContextKeyExpr.has(inQuickInputContextKeyValue);3940export const quickInputAlignmentContextKeyValue = 'quickInputAlignment';41export const QuickInputAlignmentContextKey = new RawContextKey<'top' | 'center' | undefined>(quickInputAlignmentContextKeyValue, 'top', localize('quickInputAlignment', "The alignment of the quick input"));4243export const quickInputTypeContextKeyValue = 'quickInputType';44export const QuickInputTypeContextKey = new RawContextKey<QuickInputType>(quickInputTypeContextKeyValue, undefined, localize('quickInputType', "The type of the currently visible quick input"));4546export const endOfQuickInputBoxContextKeyValue = 'cursorAtEndOfQuickInputBox';47export const EndOfQuickInputBoxContextKey = new RawContextKey<boolean>(endOfQuickInputBoxContextKeyValue, false, localize('cursorAtEndOfQuickInputBox', "Whether the cursor in the quick input is at the end of the input box"));48export const endOfQuickInputBoxContext = ContextKeyExpr.has(endOfQuickInputBoxContextKeyValue);4950export interface IQuickInputOptions {51idPrefix: string;52container: HTMLElement;53ignoreFocusOut(): boolean;54backKeybindingLabel(): string | undefined;55setContextKey(id?: string): void;56linkOpenerDelegate(content: string): void;57returnFocus(): void;58/**59* @todo With IHover in vs/editor, can we depend on the service directly60* instead of passing it through a hover delegate?61*/62hoverDelegate: IHoverDelegate;63styles: IQuickInputStyles;64}6566export interface IQuickInputStyles {67readonly widget: IQuickInputWidgetStyles;68readonly inputBox: IInputBoxStyles;69readonly toggle: IToggleStyles;70readonly countBadge: ICountBadgeStyles;71readonly button: IButtonStyles;72readonly progressBar: IProgressBarStyles;73readonly keybindingLabel: IKeybindingLabelStyles;74readonly list: IListStyles;75readonly pickerGroup: { pickerGroupBorder: string | undefined; pickerGroupForeground: string | undefined };76}7778export interface IQuickInputWidgetStyles {79readonly quickInputBackground: string | undefined;80readonly quickInputForeground: string | undefined;81readonly quickInputTitleBackground: string | undefined;82readonly widgetBorder: string | undefined;83readonly widgetShadow: string | undefined;84}8586export type Writeable<T> = { -readonly [P in keyof T]: T[P] };8788export const backButton = {89iconClass: ThemeIcon.asClassName(Codicon.quickInputBack),90tooltip: localize('quickInput.back', "Back"),91handle: -1 // TODO92};9394export interface QuickInputUI {95container: HTMLElement;96styleSheet: HTMLStyleElement;97leftActionBar: ActionBar;98titleBar: HTMLElement;99title: HTMLElement;100description1: HTMLElement;101description2: HTMLElement;102widget: HTMLElement;103rightActionBar: ActionBar;104inlineActionBar: ActionBar;105checkAll: HTMLInputElement;106inputContainer: HTMLElement;107filterContainer: HTMLElement;108inputBox: QuickInputBox;109visibleCountContainer: HTMLElement;110visibleCount: CountBadge;111countContainer: HTMLElement;112count: CountBadge;113okContainer: HTMLElement;114ok: Button;115message: HTMLElement;116customButtonContainer: HTMLElement;117customButton: Button;118progressBar: ProgressBar;119list: QuickInputTree;120onDidAccept: Event<void>;121onDidCustom: Event<void>;122onDidTriggerButton: Event<IQuickInputButton>;123ignoreFocusOut: boolean;124keyMods: Writeable<IKeyMods>;125show(controller: QuickInput): void;126setVisibilities(visibilities: Visibilities): void;127setEnabled(enabled: boolean): void;128setContextKey(contextKey?: string): void;129linkOpenerDelegate(content: string): void;130hide(): void;131}132133export type Visibilities = {134title?: boolean;135description?: boolean;136checkAll?: boolean;137inputBox?: boolean;138checkBox?: boolean;139visibleCount?: boolean;140count?: boolean;141message?: boolean;142list?: boolean;143ok?: boolean;144customButton?: boolean;145progressBar?: boolean;146};147148abstract class QuickInput extends Disposable implements IQuickInput {149protected static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");150151private _title: string | undefined;152private _description: string | undefined;153private _widget: HTMLElement | undefined;154private _widgetUpdated = false;155private _steps: number | undefined;156private _totalSteps: number | undefined;157protected visible = false;158private _enabled = true;159private _contextKey: string | undefined;160private _busy = false;161private _ignoreFocusOut = false;162private _leftButtons: IQuickInputButton[] = [];163private _rightButtons: IQuickInputButton[] = [];164private _inlineButtons: IQuickInputButton[] = [];165private buttonsUpdated = false;166private _toggles: IQuickInputToggle[] = [];167private togglesUpdated = false;168protected noValidationMessage = QuickInput.noPromptMessage;169private _validationMessage: string | undefined;170private _lastValidationMessage: string | undefined;171private _severity: Severity = Severity.Ignore;172private _lastSeverity: Severity | undefined;173private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());174private readonly onDidHideEmitter = this._register(new Emitter<IQuickInputHideEvent>());175private readonly onWillHideEmitter = this._register(new Emitter<IQuickInputHideEvent>());176private readonly onDisposeEmitter = this._register(new Emitter<void>());177178protected readonly visibleDisposables = this._register(new DisposableStore());179180private busyDelay: TimeoutTimer | undefined;181182abstract type: QuickInputType;183184constructor(185protected ui: QuickInputUI186) {187super();188}189190get title() {191return this._title;192}193194set title(title: string | undefined) {195this._title = title;196this.update();197}198199get description() {200return this._description;201}202203set description(description: string | undefined) {204this._description = description;205this.update();206}207208get widget() {209return this._widget;210}211212set widget(widget: unknown | undefined) {213if (!(dom.isHTMLElement(widget))) {214return;215}216if (this._widget !== widget) {217this._widget = widget;218this._widgetUpdated = true;219this.update();220}221}222223get step() {224return this._steps;225}226227set step(step: number | undefined) {228this._steps = step;229this.update();230}231232get totalSteps() {233return this._totalSteps;234}235236set totalSteps(totalSteps: number | undefined) {237this._totalSteps = totalSteps;238this.update();239}240241get enabled() {242return this._enabled;243}244245set enabled(enabled: boolean) {246this._enabled = enabled;247this.update();248}249250get contextKey() {251return this._contextKey;252}253254set contextKey(contextKey: string | undefined) {255this._contextKey = contextKey;256this.update();257}258259get busy() {260return this._busy;261}262263set busy(busy: boolean) {264this._busy = busy;265this.update();266}267268get ignoreFocusOut() {269return this._ignoreFocusOut;270}271272set ignoreFocusOut(ignoreFocusOut: boolean) {273const shouldUpdate = this._ignoreFocusOut !== ignoreFocusOut && !isIOS;274this._ignoreFocusOut = ignoreFocusOut && !isIOS;275if (shouldUpdate) {276this.update();277}278}279280protected get titleButtons() {281return this._leftButtons.length282? [...this._leftButtons, this._rightButtons]283: this._rightButtons;284}285286get buttons() {287return [288...this._leftButtons,289...this._rightButtons,290...this._inlineButtons291];292}293294set buttons(buttons: IQuickInputButton[]) {295this._leftButtons = buttons.filter(b => b === backButton);296this._rightButtons = buttons.filter(b => b !== backButton && b.location !== QuickInputButtonLocation.Inline);297this._inlineButtons = buttons.filter(b => b.location === QuickInputButtonLocation.Inline);298this.buttonsUpdated = true;299this.update();300}301302get toggles() {303return this._toggles;304}305306set toggles(toggles: IQuickInputToggle[]) {307this._toggles = toggles ?? [];308this.togglesUpdated = true;309this.update();310}311312get validationMessage() {313return this._validationMessage;314}315316set validationMessage(validationMessage: string | undefined) {317this._validationMessage = validationMessage;318this.update();319}320321get severity() {322return this._severity;323}324325set severity(severity: Severity) {326this._severity = severity;327this.update();328}329330readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event;331332show(): void {333if (this.visible) {334return;335}336this.visibleDisposables.add(337this.ui.onDidTriggerButton(button => {338if (this.buttons.indexOf(button) !== -1) {339this.onDidTriggerButtonEmitter.fire(button);340}341}),342);343this.ui.show(this);344345// update properties in the controller that get reset in the ui.show() call346this.visible = true;347// This ensures the message/prompt gets rendered348this._lastValidationMessage = undefined;349// This ensures the input box has the right severity applied350this._lastSeverity = undefined;351if (this.buttons.length) {352// if there are buttons, the ui.show() clears them out of the UI so we should353// rerender them.354this.buttonsUpdated = true;355}356if (this.toggles.length) {357// if there are toggles, the ui.show() clears them out of the UI so we should358// rerender them.359this.togglesUpdated = true;360}361362this.update();363}364365hide(): void {366if (!this.visible) {367return;368}369this.ui.hide();370}371372didHide(reason = QuickInputHideReason.Other): void {373this.visible = false;374this.visibleDisposables.clear();375this.onDidHideEmitter.fire({ reason });376}377378readonly onDidHide = this.onDidHideEmitter.event;379380willHide(reason = QuickInputHideReason.Other): void {381this.onWillHideEmitter.fire({ reason });382}383readonly onWillHide = this.onWillHideEmitter.event;384385protected update() {386if (!this.visible) {387return;388}389const title = this.getTitle();390if (title && this.ui.title.textContent !== title) {391this.ui.title.textContent = title;392} else if (!title && this.ui.title.innerHTML !== ' ') {393this.ui.title.innerText = '\u00a0';394}395const description = this.getDescription();396if (this.ui.description1.textContent !== description) {397this.ui.description1.textContent = description;398}399if (this.ui.description2.textContent !== description) {400this.ui.description2.textContent = description;401}402if (this._widgetUpdated) {403this._widgetUpdated = false;404if (this._widget) {405dom.reset(this.ui.widget, this._widget);406} else {407dom.reset(this.ui.widget);408}409}410if (this.busy && !this.busyDelay) {411this.busyDelay = new TimeoutTimer();412this.busyDelay.setIfNotSet(() => {413if (this.visible) {414this.ui.progressBar.infinite();415}416}, 800);417}418if (!this.busy && this.busyDelay) {419this.ui.progressBar.stop();420this.busyDelay.cancel();421this.busyDelay = undefined;422}423if (this.buttonsUpdated) {424this.buttonsUpdated = false;425this.ui.leftActionBar.clear();426const leftButtons = this._leftButtons427.map((button, index) => quickInputButtonToAction(428button,429`id-${index}`,430async () => this.onDidTriggerButtonEmitter.fire(button)431));432this.ui.leftActionBar.push(leftButtons, { icon: true, label: false });433this.ui.rightActionBar.clear();434const rightButtons = this._rightButtons435.map((button, index) => quickInputButtonToAction(436button,437`id-${index}`,438async () => this.onDidTriggerButtonEmitter.fire(button)439));440this.ui.rightActionBar.push(rightButtons, { icon: true, label: false });441this.ui.inlineActionBar.clear();442const inlineButtons = this._inlineButtons443.map((button, index) => quickInputButtonToAction(444button,445`id-${index}`,446async () => this.onDidTriggerButtonEmitter.fire(button)447));448this.ui.inlineActionBar.push(inlineButtons, { icon: true, label: false });449}450if (this.togglesUpdated) {451this.togglesUpdated = false;452// HACK: Filter out toggles here that are not concrete Toggle objects. This is to workaround453// a layering issue as quick input's interface is in common but Toggle is in browser and454// it requires a HTMLElement on its interface455const concreteToggles = this.toggles?.filter(opts => opts instanceof Toggle) as Toggle[] ?? [];456this.ui.inputBox.toggles = concreteToggles;457}458this.ui.ignoreFocusOut = this.ignoreFocusOut;459this.ui.setEnabled(this.enabled);460this.ui.setContextKey(this.contextKey);461462const validationMessage = this.validationMessage || this.noValidationMessage;463if (this._lastValidationMessage !== validationMessage) {464this._lastValidationMessage = validationMessage;465dom.reset(this.ui.message);466renderQuickInputDescription(validationMessage, this.ui.message, {467callback: (content) => {468this.ui.linkOpenerDelegate(content);469},470disposables: this.visibleDisposables,471});472}473if (this._lastSeverity !== this.severity) {474this._lastSeverity = this.severity;475this.showMessageDecoration(this.severity);476}477}478479private getTitle() {480if (this.title && this.step) {481return `${this.title} (${this.getSteps()})`;482}483if (this.title) {484return this.title;485}486if (this.step) {487return this.getSteps();488}489return '';490}491492private getDescription() {493return this.description || '';494}495496private getSteps() {497if (this.step && this.totalSteps) {498return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps);499}500if (this.step) {501return String(this.step);502}503return '';504}505506protected showMessageDecoration(severity: Severity) {507this.ui.inputBox.showDecoration(severity);508if (severity !== Severity.Ignore) {509const styles = this.ui.inputBox.stylesForType(severity);510this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : '';511this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';512this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : '';513this.ui.message.style.marginBottom = '-2px';514} else {515this.ui.message.style.color = '';516this.ui.message.style.backgroundColor = '';517this.ui.message.style.border = '';518this.ui.message.style.marginBottom = '';519}520}521522readonly onDispose = this.onDisposeEmitter.event;523524override dispose(): void {525this.hide();526this.onDisposeEmitter.fire();527528super.dispose();529}530}531532export class QuickPick<T extends IQuickPickItem, O extends { useSeparators: boolean } = { useSeparators: false }> extends QuickInput implements IQuickPick<T, O> {533534private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");535536private _value = '';537private _ariaLabel: string | undefined;538private _placeholder: string | undefined;539private readonly onDidChangeValueEmitter = this._register(new Emitter<string>());540private readonly onWillAcceptEmitter = this._register(new Emitter<IQuickPickWillAcceptEvent>());541private readonly onDidAcceptEmitter = this._register(new Emitter<IQuickPickDidAcceptEvent>());542private readonly onDidCustomEmitter = this._register(new Emitter<void>());543private _items: O extends { useSeparators: true } ? Array<T | IQuickPickSeparator> : Array<T> = [];544private itemsUpdated = false;545private _canSelectMany = false;546private _canAcceptInBackground = false;547private _matchOnDescription = false;548private _matchOnDetail = false;549private _matchOnLabel = true;550private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';551private _sortByLabel = true;552private _keepScrollPosition = false;553private _itemActivation = ItemActivation.FIRST;554private _activeItems: T[] = [];555private activeItemsUpdated = false;556private activeItemsToConfirm: T[] | null = [];557private readonly onDidChangeActiveEmitter = this._register(new Emitter<T[]>());558private _selectedItems: T[] = [];559private selectedItemsUpdated = false;560private selectedItemsToConfirm: T[] | null = [];561private readonly onDidChangeSelectionEmitter = this._register(new Emitter<T[]>());562private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter<IQuickPickItemButtonEvent<T>>());563private readonly onDidTriggerSeparatorButtonEmitter = this._register(new Emitter<IQuickPickSeparatorButtonEvent>());564private _valueSelection: Readonly<[number, number]> | undefined;565private valueSelectionUpdated = true;566private _ok: boolean | 'default' = 'default';567private _okLabel: string | undefined;568private _customButton = false;569private _customButtonLabel: string | undefined;570private _customButtonHover: string | undefined;571private _quickNavigate: IQuickNavigateConfiguration | undefined;572private _hideInput: boolean | undefined;573private _hideCountBadge: boolean | undefined;574private _hideCheckAll: boolean | undefined;575private _focusEventBufferer = new EventBufferer();576577readonly type = QuickInputType.QuickPick;578579get quickNavigate() {580return this._quickNavigate;581}582583set quickNavigate(quickNavigate: IQuickNavigateConfiguration | undefined) {584this._quickNavigate = quickNavigate;585this.update();586}587588get value() {589return this._value;590}591592set value(value: string) {593this.doSetValue(value);594}595596private doSetValue(value: string, skipUpdate?: boolean): void {597if (this._value !== value) {598this._value = value;599if (!skipUpdate) {600this.update();601}602if (this.visible) {603const didFilter = this.ui.list.filter(this.filterValue(this._value));604if (didFilter) {605this.trySelectFirst();606}607}608this.onDidChangeValueEmitter.fire(this._value);609}610}611612filterValue = (value: string) => value;613614set ariaLabel(ariaLabel: string | undefined) {615this._ariaLabel = ariaLabel;616this.update();617}618619get ariaLabel() {620return this._ariaLabel;621}622623get placeholder() {624return this._placeholder;625}626627set placeholder(placeholder: string | undefined) {628this._placeholder = placeholder;629this.update();630}631632onDidChangeValue = this.onDidChangeValueEmitter.event;633634onWillAccept = this.onWillAcceptEmitter.event;635onDidAccept = this.onDidAcceptEmitter.event;636637onDidCustom = this.onDidCustomEmitter.event;638639get items() {640return this._items;641}642643get scrollTop() {644return this.ui.list.scrollTop;645}646647private set scrollTop(scrollTop: number) {648this.ui.list.scrollTop = scrollTop;649}650651set items(items: O extends { useSeparators: true } ? Array<T | IQuickPickSeparator> : Array<T>) {652this._items = items;653this.itemsUpdated = true;654this.update();655}656657get canSelectMany() {658return this._canSelectMany;659}660661set canSelectMany(canSelectMany: boolean) {662this._canSelectMany = canSelectMany;663this.update();664}665666get canAcceptInBackground() {667return this._canAcceptInBackground;668}669670set canAcceptInBackground(canAcceptInBackground: boolean) {671this._canAcceptInBackground = canAcceptInBackground;672}673674get matchOnDescription() {675return this._matchOnDescription;676}677678set matchOnDescription(matchOnDescription: boolean) {679this._matchOnDescription = matchOnDescription;680this.update();681}682683get matchOnDetail() {684return this._matchOnDetail;685}686687set matchOnDetail(matchOnDetail: boolean) {688this._matchOnDetail = matchOnDetail;689this.update();690}691692get matchOnLabel() {693return this._matchOnLabel;694}695696set matchOnLabel(matchOnLabel: boolean) {697this._matchOnLabel = matchOnLabel;698this.update();699}700701get matchOnLabelMode() {702return this._matchOnLabelMode;703}704705set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') {706this._matchOnLabelMode = matchOnLabelMode;707this.update();708}709710get sortByLabel() {711return this._sortByLabel;712}713714set sortByLabel(sortByLabel: boolean) {715this._sortByLabel = sortByLabel;716this.update();717}718719get keepScrollPosition() {720return this._keepScrollPosition;721}722723set keepScrollPosition(keepScrollPosition: boolean) {724this._keepScrollPosition = keepScrollPosition;725}726727get itemActivation() {728return this._itemActivation;729}730731set itemActivation(itemActivation: ItemActivation) {732this._itemActivation = itemActivation;733}734735get activeItems() {736return this._activeItems;737}738739set activeItems(activeItems: T[]) {740this._activeItems = activeItems;741this.activeItemsUpdated = true;742this.update();743}744745onDidChangeActive = this.onDidChangeActiveEmitter.event;746747get selectedItems() {748return this._selectedItems;749}750751set selectedItems(selectedItems: T[]) {752this._selectedItems = selectedItems;753this.selectedItemsUpdated = true;754this.update();755}756757get keyMods() {758if (this._quickNavigate) {759// Disable keyMods when quick navigate is enabled760// because in this model the interaction is purely761// keyboard driven and Ctrl/Alt are typically762// pressed and hold during this interaction.763return NO_KEY_MODS;764}765return this.ui.keyMods;766}767768get valueSelection() {769const selection = this.ui.inputBox.getSelection();770if (!selection) {771return undefined;772}773return [selection.start, selection.end];774}775776set valueSelection(valueSelection: Readonly<[number, number]> | undefined) {777this._valueSelection = valueSelection;778this.valueSelectionUpdated = true;779this.update();780}781782get customButton() {783return this._customButton;784}785786set customButton(showCustomButton: boolean) {787this._customButton = showCustomButton;788this.update();789}790791get customLabel() {792return this._customButtonLabel;793}794795set customLabel(label: string | undefined) {796this._customButtonLabel = label;797this.update();798}799800get customHover() {801return this._customButtonHover;802}803804set customHover(hover: string | undefined) {805this._customButtonHover = hover;806this.update();807}808809get ok() {810return this._ok;811}812813set ok(showOkButton: boolean | 'default') {814this._ok = showOkButton;815this.update();816}817818get okLabel() {819return this._okLabel ?? localize('ok', "OK");820}821822set okLabel(okLabel: string | undefined) {823this._okLabel = okLabel;824this.update();825}826827inputHasFocus(): boolean {828return this.visible ? this.ui.inputBox.hasFocus() : false;829}830831focusOnInput() {832this.ui.inputBox.setFocus();833}834835get hideInput() {836return !!this._hideInput;837}838839set hideInput(hideInput: boolean) {840this._hideInput = hideInput;841this.update();842}843844get hideCountBadge() {845return !!this._hideCountBadge;846}847848set hideCountBadge(hideCountBadge: boolean) {849this._hideCountBadge = hideCountBadge;850this.update();851}852853get hideCheckAll() {854return !!this._hideCheckAll;855}856857set hideCheckAll(hideCheckAll: boolean) {858this._hideCheckAll = hideCheckAll;859this.update();860}861862onDidChangeSelection = this.onDidChangeSelectionEmitter.event;863864onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;865866onDidTriggerSeparatorButton = this.onDidTriggerSeparatorButtonEmitter.event;867868private trySelectFirst() {869if (!this.canSelectMany) {870this.ui.list.focus(QuickPickFocus.First);871}872}873874override show() {875if (!this.visible) {876this.visibleDisposables.add(877this.ui.inputBox.onDidChange(value => {878this.doSetValue(value, true /* skip update since this originates from the UI */);879}));880this.visibleDisposables.add(this.ui.onDidAccept(() => {881if (this.canSelectMany) {882// if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite883// `_selectedItems`. In that case, we should emit one with an empty array to ensure that884// `.selectedItems` is up to date.885if (!this.ui.list.getCheckedElements().length) {886this._selectedItems = [];887this.onDidChangeSelectionEmitter.fire(this.selectedItems);888}889} else if (this.activeItems[0]) {890// For single-select, we set `selectedItems` to the item that was accepted.891this._selectedItems = [this.activeItems[0]];892this.onDidChangeSelectionEmitter.fire(this.selectedItems);893}894this.handleAccept(false);895}));896this.visibleDisposables.add(this.ui.onDidCustom(() => {897this.onDidCustomEmitter.fire();898}));899this.visibleDisposables.add(this._focusEventBufferer.wrapEvent(900this.ui.list.onDidChangeFocus,901// Only fire the last event902(_, e) => e903)(focusedItems => {904if (this.activeItemsUpdated) {905return; // Expect another event.906}907if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {908return;909}910this._activeItems = focusedItems as T[];911this.onDidChangeActiveEmitter.fire(focusedItems as T[]);912}));913this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => {914if (this.canSelectMany && !selectedItems.some(i => i.pickable === false)) {915if (selectedItems.length) {916this.ui.list.setSelectedElements([]);917}918return;919}920if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {921return;922}923this._selectedItems = selectedItems as T[];924this.onDidChangeSelectionEmitter.fire(selectedItems as T[]);925if (selectedItems.length) {926this.handleAccept(dom.isMouseEvent(event) && event.button === 1 /* mouse middle click */);927}928}));929this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => {930if (!this.canSelectMany || !this.visible) {931return;932}933if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {934return;935}936this._selectedItems = checkedItems as T[];937this.onDidChangeSelectionEmitter.fire(checkedItems as T[]);938}));939this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>)));940this.visibleDisposables.add(this.ui.list.onSeparatorButtonTriggered(event => this.onDidTriggerSeparatorButtonEmitter.fire(event)));941this.visibleDisposables.add(this.registerQuickNavigation());942this.valueSelectionUpdated = true;943}944super.show(); // TODO: Why have show() bubble up while update() trickles down?945}946947private handleAccept(inBackground: boolean): void {948949// Figure out veto via `onWillAccept` event950let veto = false;951this.onWillAcceptEmitter.fire({ veto: () => veto = true });952953// Continue with `onDidAccept` if no veto954if (!veto) {955this.onDidAcceptEmitter.fire({ inBackground });956}957}958959private registerQuickNavigation() {960return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {961if (this.canSelectMany || !this._quickNavigate) {962return;963}964965const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e);966const keyCode = keyboardEvent.keyCode;967968// Select element when keys are pressed that signal it969const quickNavKeys = this._quickNavigate.keybindings;970const wasTriggerKeyPressed = quickNavKeys.some(k => {971const chords = k.getChords();972if (chords.length > 1) {973return false;974}975976if (chords[0].shiftKey && keyCode === KeyCode.Shift) {977if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {978return false; // this is an optimistic check for the shift key being used to navigate back in quick input979}980981return true;982}983984if (chords[0].altKey && keyCode === KeyCode.Alt) {985return true;986}987988if (chords[0].ctrlKey && keyCode === KeyCode.Ctrl) {989return true;990}991992if (chords[0].metaKey && keyCode === KeyCode.Meta) {993return true;994}995996return false;997});998999if (wasTriggerKeyPressed) {1000if (this.activeItems[0]) {1001this._selectedItems = [this.activeItems[0]];1002this.onDidChangeSelectionEmitter.fire(this.selectedItems);1003this.handleAccept(false);1004}1005// Unset quick navigate after press. It is only valid once1006// and should not result in any behaviour change afterwards1007// if the picker remains open because there was no active item1008this._quickNavigate = undefined;1009}1010});1011}10121013protected override update() {1014if (!this.visible) {1015return;1016}1017// store the scrollTop before it is reset1018const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0;1019const hasDescription = !!this.description;1020const visibilities: Visibilities = {1021title: !!this.title || !!this.step || !!this.titleButtons.length,1022description: hasDescription,1023checkAll: this.canSelectMany && !this._hideCheckAll,1024checkBox: this.canSelectMany,1025inputBox: !this._hideInput,1026progressBar: !this._hideInput || hasDescription,1027visibleCount: true,1028count: this.canSelectMany && !this._hideCountBadge,1029ok: this.ok === 'default' ? this.canSelectMany : this.ok,1030list: true,1031message: !!this.validationMessage,1032customButton: this.customButton1033};1034this.ui.setVisibilities(visibilities);1035super.update();1036if (this.ui.inputBox.value !== this.value) {1037this.ui.inputBox.value = this.value;1038}1039if (this.valueSelectionUpdated) {1040this.valueSelectionUpdated = false;1041this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });1042}1043if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {1044this.ui.inputBox.placeholder = (this.placeholder || '');1045}10461047let ariaLabel = this.ariaLabel;1048// Only set aria label to the input box placeholder if we actually have an input box.1049if (!ariaLabel && visibilities.inputBox) {1050ariaLabel = this.placeholder || QuickPick.DEFAULT_ARIA_LABEL;1051// If we have a title, include it in the aria label.1052if (this.title) {1053ariaLabel += ` - ${this.title}`;1054}1055}1056if (this.ui.list.ariaLabel !== ariaLabel) {1057this.ui.list.ariaLabel = ariaLabel ?? null;1058}1059this.ui.list.matchOnDescription = this.matchOnDescription;1060this.ui.list.matchOnDetail = this.matchOnDetail;1061this.ui.list.matchOnLabel = this.matchOnLabel;1062this.ui.list.matchOnLabelMode = this.matchOnLabelMode;1063this.ui.list.sortByLabel = this.sortByLabel;1064if (this.itemsUpdated) {1065this.itemsUpdated = false;1066this._focusEventBufferer.bufferEvents(() => {1067this.ui.list.setElements(this.items);1068// We want focus to exist in the list if there are items so that space can be used to toggle1069this.ui.list.shouldLoop = !this.canSelectMany;1070this.ui.list.filter(this.filterValue(this.ui.inputBox.value));1071switch (this._itemActivation) {1072case ItemActivation.NONE:1073this._itemActivation = ItemActivation.FIRST; // only valid once, then unset1074break;1075case ItemActivation.SECOND:1076this.ui.list.focus(QuickPickFocus.Second);1077this._itemActivation = ItemActivation.FIRST; // only valid once, then unset1078break;1079case ItemActivation.LAST:1080this.ui.list.focus(QuickPickFocus.Last);1081this._itemActivation = ItemActivation.FIRST; // only valid once, then unset1082break;1083default:1084this.trySelectFirst();1085break;1086}1087});1088}1089if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {1090if (this.canSelectMany) {1091this.ui.list.clearFocus();1092} else {1093this.trySelectFirst();1094}1095}1096if (this.activeItemsUpdated) {1097this.activeItemsUpdated = false;1098this.activeItemsToConfirm = this._activeItems;1099this.ui.list.setFocusedElements(this.activeItems);1100if (this.activeItemsToConfirm === this._activeItems) {1101this.activeItemsToConfirm = null;1102}1103}1104if (this.selectedItemsUpdated) {1105this.selectedItemsUpdated = false;1106this.selectedItemsToConfirm = this._selectedItems;1107if (this.canSelectMany) {1108this.ui.list.setCheckedElements(this.selectedItems);1109} else {1110this.ui.list.setSelectedElements(this.selectedItems);1111}1112if (this.selectedItemsToConfirm === this._selectedItems) {1113this.selectedItemsToConfirm = null;1114}1115}1116this.ui.ok.label = this.okLabel || '';1117this.ui.customButton.label = this.customLabel || '';1118this.ui.customButton.element.title = this.customHover || '';1119if (!visibilities.inputBox) {1120// we need to move focus into the tree to detect keybindings1121// properly when the input box is not visible (quick nav)1122this.ui.list.domFocus();11231124// Focus the first element in the list if multiselect is enabled1125if (this.canSelectMany) {1126this.ui.list.focus(QuickPickFocus.First);1127}1128}11291130// Set the scroll position to what it was before updating the items1131if (this.keepScrollPosition) {1132this.scrollTop = scrollTopBefore;1133}1134}11351136focus(focus: QuickPickFocus): void {1137this.ui.list.focus(focus);1138// To allow things like space to check/uncheck items1139if (this.canSelectMany) {1140this.ui.list.domFocus();1141}1142}11431144accept(inBackground?: boolean | undefined): void {1145if (inBackground && !this._canAcceptInBackground) {1146return; // needs to be enabled1147}11481149if (this.activeItems[0]) {1150this._selectedItems = [this.activeItems[0]];1151this.onDidChangeSelectionEmitter.fire(this.selectedItems);1152this.handleAccept(inBackground ?? false);1153}1154}1155}11561157export class InputBox extends QuickInput implements IInputBox {1158private _value = '';1159private _valueSelection: Readonly<[number, number]> | undefined;1160private valueSelectionUpdated = true;1161private _placeholder: string | undefined;1162private _password = false;1163private _prompt: string | undefined;1164private readonly onDidValueChangeEmitter = this._register(new Emitter<string>());1165private readonly onDidAcceptEmitter = this._register(new Emitter<void>());11661167readonly type = QuickInputType.InputBox;11681169get value() {1170return this._value;1171}11721173set value(value: string) {1174this._value = value || '';1175this.update();1176}11771178get valueSelection() {1179const selection = this.ui.inputBox.getSelection();1180if (!selection) {1181return undefined;1182}1183return [selection.start, selection.end];1184}11851186set valueSelection(valueSelection: Readonly<[number, number]> | undefined) {1187this._valueSelection = valueSelection;1188this.valueSelectionUpdated = true;1189this.update();1190}11911192get placeholder() {1193return this._placeholder;1194}11951196set placeholder(placeholder: string | undefined) {1197this._placeholder = placeholder;1198this.update();1199}12001201get password() {1202return this._password;1203}12041205set password(password: boolean) {1206this._password = password;1207this.update();1208}12091210get prompt() {1211return this._prompt;1212}12131214set prompt(prompt: string | undefined) {1215this._prompt = prompt;1216this.noValidationMessage = prompt1217? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt)1218: QuickInput.noPromptMessage;1219this.update();1220}12211222readonly onDidChangeValue = this.onDidValueChangeEmitter.event;12231224readonly onDidAccept = this.onDidAcceptEmitter.event;12251226override show() {1227if (!this.visible) {1228this.visibleDisposables.add(1229this.ui.inputBox.onDidChange(value => {1230if (value === this.value) {1231return;1232}1233this._value = value;1234this.onDidValueChangeEmitter.fire(value);1235}));1236this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()));1237this.valueSelectionUpdated = true;1238}1239super.show();1240}12411242protected override update() {1243if (!this.visible) {1244return;1245}12461247this.ui.container.classList.remove('hidden-input');1248const visibilities: Visibilities = {1249title: !!this.title || !!this.step || !!this.titleButtons.length,1250description: !!this.description || !!this.step,1251inputBox: true,1252message: true,1253progressBar: true1254};12551256this.ui.setVisibilities(visibilities);1257super.update();1258if (this.ui.inputBox.value !== this.value) {1259this.ui.inputBox.value = this.value;1260}1261if (this.valueSelectionUpdated) {1262this.valueSelectionUpdated = false;1263this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });1264}1265if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {1266this.ui.inputBox.placeholder = (this.placeholder || '');1267}1268if (this.ui.inputBox.password !== this.password) {1269this.ui.inputBox.password = this.password;1270}1271}1272}12731274export class QuickWidget extends QuickInput implements IQuickWidget {1275readonly type = QuickInputType.QuickWidget;12761277protected override update() {1278if (!this.visible) {1279return;1280}12811282const visibilities: Visibilities = {1283title: !!this.title || !!this.step || !!this.titleButtons.length,1284description: !!this.description || !!this.step1285};12861287this.ui.setVisibilities(visibilities);1288super.update();1289}1290}12911292export class QuickInputHoverDelegate extends WorkbenchHoverDelegate {12931294constructor(1295@IConfigurationService configurationService: IConfigurationService,1296@IHoverService hoverService: IHoverService1297) {1298super('element', undefined, (options) => this.getOverrideOptions(options), configurationService, hoverService);1299}13001301private getOverrideOptions(options: IHoverDelegateOptions): Partial<IHoverOptions> {1302// Only show the hover hint if the content is of a decent size1303const showHoverHint = (1304dom.isHTMLElement(options.content)1305? options.content.textContent ?? ''1306: typeof options.content === 'string'1307? options.content1308: options.content.value1309).includes('\n');13101311return {1312persistence: {1313hideOnKeyDown: false,1314},1315appearance: {1316showHoverHint,1317skipFadeInAnimation: true,1318},1319};1320}1321}132213231324