Path: blob/main/src/vs/base/browser/ui/findinput/findInput.ts
5240 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 '../../dom.js';6import { IKeyboardEvent } from '../../keyboardEvent.js';7import { IMouseEvent } from '../../mouseEvent.js';8import { IToggleStyles, Toggle } from '../toggle/toggle.js';9import { IContextViewProvider } from '../contextview/contextview.js';10import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from './findInputToggles.js';11import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from '../inputbox/inputBox.js';12import { Widget } from '../widget.js';13import { Emitter, Event } from '../../../common/event.js';14import { KeyCode } from '../../../common/keyCodes.js';15import { IAction } from '../../../common/actions.js';16import type { IActionViewItemProvider } from '../actionbar/actionbar.js';17import './findInput.css';18import * as nls from '../../../../nls.js';19import { DisposableStore, MutableDisposable } from '../../../common/lifecycle.js';20import { IHistory } from '../../../common/history.js';21import type { IHoverLifecycleOptions } from '../hover/hover.js';222324export interface IFindInputOptions {25readonly placeholder?: string;26readonly width?: number;27readonly validation?: IInputValidator;28readonly label: string;29readonly flexibleHeight?: boolean;30readonly flexibleWidth?: boolean;31readonly flexibleMaxHeight?: number;3233readonly showCommonFindToggles?: boolean;34readonly appendCaseSensitiveLabel?: string;35readonly appendWholeWordsLabel?: string;36readonly appendRegexLabel?: string;37readonly additionalToggles?: Toggle[];38readonly actions?: ReadonlyArray<IAction>;39readonly actionViewItemProvider?: IActionViewItemProvider;40readonly showHistoryHint?: () => boolean;41readonly toggleStyles: IToggleStyles;42readonly inputBoxStyles: IInputBoxStyles;43readonly history?: IHistory<string>;44readonly hoverLifecycleOptions?: IHoverLifecycleOptions;45readonly hideHoverOnValueChange?: boolean;46}4748const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input");4950export class FindInput extends Widget {5152static readonly OPTION_CHANGE: string = 'optionChange';5354private placeholder: string;55private validation?: IInputValidator;56private label: string;57private readonly showCommonFindToggles: boolean;58private fixFocusOnOptionClickEnabled = true;59private imeSessionInProgress = false;60private readonly additionalTogglesDisposables: MutableDisposable<DisposableStore> = this._register(new MutableDisposable());6162protected readonly controls: HTMLDivElement;63protected readonly regex?: RegexToggle;64protected readonly wholeWords?: WholeWordsToggle;65protected readonly caseSensitive?: CaseSensitiveToggle;66protected additionalToggles: Toggle[] = [];67public readonly domNode: HTMLElement;68public readonly inputBox: HistoryInputBox;6970private readonly _onDidOptionChange = this._register(new Emitter<boolean>());71public get onDidOptionChange(): Event<boolean /* via keyboard */> { return this._onDidOptionChange.event; }7273private readonly _onKeyDown = this._register(new Emitter<IKeyboardEvent>());74public get onKeyDown(): Event<IKeyboardEvent> { return this._onKeyDown.event; }7576private readonly _onMouseDown = this._register(new Emitter<IMouseEvent>());77public get onMouseDown(): Event<IMouseEvent> { return this._onMouseDown.event; }7879private readonly _onInput = this._register(new Emitter<void>());80public get onInput(): Event<void> { return this._onInput.event; }8182private readonly _onKeyUp = this._register(new Emitter<IKeyboardEvent>());83public get onKeyUp(): Event<IKeyboardEvent> { return this._onKeyUp.event; }8485private _onCaseSensitiveKeyDown = this._register(new Emitter<IKeyboardEvent>());86public get onCaseSensitiveKeyDown(): Event<IKeyboardEvent> { return this._onCaseSensitiveKeyDown.event; }8788private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>());89public get onRegexKeyDown(): Event<IKeyboardEvent> { return this._onRegexKeyDown.event; }9091constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IFindInputOptions) {92super();93this.placeholder = options.placeholder || '';94this.validation = options.validation;95this.label = options.label || NLS_DEFAULT_LABEL;96this.showCommonFindToggles = !!options.showCommonFindToggles;9798const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || '';99const appendWholeWordsLabel = options.appendWholeWordsLabel || '';100const appendRegexLabel = options.appendRegexLabel || '';101const flexibleHeight = !!options.flexibleHeight;102const flexibleWidth = !!options.flexibleWidth;103const flexibleMaxHeight = options.flexibleMaxHeight;104105this.domNode = document.createElement('div');106this.domNode.classList.add('monaco-findInput');107108this.inputBox = this._register(new HistoryInputBox(this.domNode, contextViewProvider, {109placeholder: this.placeholder || '',110ariaLabel: this.label || '',111validationOptions: {112validation: this.validation113},114showHistoryHint: options.showHistoryHint,115flexibleHeight,116flexibleWidth,117flexibleMaxHeight,118inputBoxStyles: options.inputBoxStyles,119history: options.history,120actions: options.actions,121actionViewItemProvider: options.actionViewItemProvider,122hideHoverOnValueChange: options.hideHoverOnValueChange123}));124125if (this.showCommonFindToggles) {126const hoverLifecycleOptions: IHoverLifecycleOptions = options?.hoverLifecycleOptions || { groupId: 'find-input' };127this.regex = this._register(new RegexToggle({128appendTitle: appendRegexLabel,129isChecked: false,130hoverLifecycleOptions,131...options.toggleStyles132}));133this._register(this.regex.onChange(viaKeyboard => {134this._onDidOptionChange.fire(viaKeyboard);135if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {136this.inputBox.focus();137}138this.validate();139}));140this._register(this.regex.onKeyDown(e => {141this._onRegexKeyDown.fire(e);142}));143144this.wholeWords = this._register(new WholeWordsToggle({145appendTitle: appendWholeWordsLabel,146isChecked: false,147hoverLifecycleOptions,148...options.toggleStyles149}));150this._register(this.wholeWords.onChange(viaKeyboard => {151this._onDidOptionChange.fire(viaKeyboard);152if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {153this.inputBox.focus();154}155this.validate();156}));157158this.caseSensitive = this._register(new CaseSensitiveToggle({159appendTitle: appendCaseSensitiveLabel,160isChecked: false,161hoverLifecycleOptions,162...options.toggleStyles163}));164this._register(this.caseSensitive.onChange(viaKeyboard => {165this._onDidOptionChange.fire(viaKeyboard);166if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {167this.inputBox.focus();168}169this.validate();170}));171this._register(this.caseSensitive.onKeyDown(e => {172this._onCaseSensitiveKeyDown.fire(e);173}));174175// Arrow-Key support to navigate between options176const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];177this.onkeydown(this.domNode, (event: IKeyboardEvent) => {178if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {179const index = indexes.indexOf(<HTMLElement>this.domNode.ownerDocument.activeElement);180if (index >= 0) {181let newIndex: number = -1;182if (event.equals(KeyCode.RightArrow)) {183newIndex = (index + 1) % indexes.length;184} else if (event.equals(KeyCode.LeftArrow)) {185if (index === 0) {186newIndex = indexes.length - 1;187} else {188newIndex = index - 1;189}190}191192if (event.equals(KeyCode.Escape)) {193indexes[index].blur();194this.inputBox.focus();195} else if (newIndex >= 0) {196indexes[newIndex].focus();197}198199dom.EventHelper.stop(event, true);200}201}202});203}204205this.controls = document.createElement('div');206this.controls.className = 'controls';207this.controls.style.display = this.showCommonFindToggles ? '' : 'none';208if (this.caseSensitive) {209this.controls.append(this.caseSensitive.domNode);210}211if (this.wholeWords) {212this.controls.appendChild(this.wholeWords.domNode);213}214if (this.regex) {215this.controls.appendChild(this.regex.domNode);216}217218this.setAdditionalToggles(options?.additionalToggles);219220if (this.controls) {221this.domNode.appendChild(this.controls);222}223224parent?.appendChild(this.domNode);225226this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionstart', (e: CompositionEvent) => {227this.imeSessionInProgress = true;228}));229this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionend', (e: CompositionEvent) => {230this.imeSessionInProgress = false;231this._onInput.fire();232}));233234this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e));235this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e));236this.oninput(this.inputBox.inputElement, (e) => this._onInput.fire());237this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e));238}239240public get isImeSessionInProgress(): boolean {241return this.imeSessionInProgress;242}243244public get onDidChange(): Event<string> {245return this.inputBox.onDidChange;246}247248public layout(style: { collapsedFindWidget: boolean; narrowFindWidget: boolean; reducedFindWidget: boolean }) {249this.inputBox.layout();250this.updateInputBoxPadding(style.collapsedFindWidget);251}252253public enable(): void {254this.domNode.classList.remove('disabled');255this.inputBox.enable();256this.regex?.enable();257this.wholeWords?.enable();258this.caseSensitive?.enable();259260for (const toggle of this.additionalToggles) {261toggle.enable();262}263}264265public disable(): void {266this.domNode.classList.add('disabled');267this.inputBox.disable();268this.regex?.disable();269this.wholeWords?.disable();270this.caseSensitive?.disable();271272for (const toggle of this.additionalToggles) {273toggle.disable();274}275}276277public setFocusInputOnOptionClick(value: boolean): void {278this.fixFocusOnOptionClickEnabled = value;279}280281public setEnabled(enabled: boolean): void {282if (enabled) {283this.enable();284} else {285this.disable();286}287}288289public setAdditionalToggles(toggles: Toggle[] | undefined): void {290for (const currentToggle of this.additionalToggles) {291currentToggle.domNode.remove();292}293this.additionalToggles = [];294this.additionalTogglesDisposables.value = new DisposableStore();295296for (const toggle of toggles ?? []) {297this.additionalTogglesDisposables.value.add(toggle);298this.controls.appendChild(toggle.domNode);299300this.additionalTogglesDisposables.value.add(toggle.onChange(viaKeyboard => {301this._onDidOptionChange.fire(viaKeyboard);302if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {303this.inputBox.focus();304}305}));306307this.additionalToggles.push(toggle);308}309310if (this.additionalToggles.length > 0) {311this.controls.style.display = '';312}313314this.updateInputBoxPadding();315}316317public setActions(actions: ReadonlyArray<IAction> | undefined, actionViewItemProvider?: IActionViewItemProvider): void {318this.inputBox.setActions(actions, actionViewItemProvider);319}320321private updateInputBoxPadding(controlsHidden = false) {322if (controlsHidden) {323this.inputBox.paddingRight = 0;324} else {325this.inputBox.paddingRight =326((this.caseSensitive?.width() ?? 0) + (this.wholeWords?.width() ?? 0) + (this.regex?.width() ?? 0))327+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);328}329}330331public clear(): void {332this.clearValidation();333this.setValue('');334this.focus();335}336337public getValue(): string {338return this.inputBox.value;339}340341public setValue(value: string): void {342if (this.inputBox.value !== value) {343this.inputBox.value = value;344}345}346347public onSearchSubmit(): void {348this.inputBox.addToHistory();349}350351public select(): void {352this.inputBox.select();353}354355public focus(): void {356this.inputBox.focus();357}358359public getCaseSensitive(): boolean {360return this.caseSensitive?.checked ?? false;361}362363public setCaseSensitive(value: boolean): void {364if (this.caseSensitive) {365this.caseSensitive.checked = value;366}367}368369public getWholeWords(): boolean {370return this.wholeWords?.checked ?? false;371}372373public setWholeWords(value: boolean): void {374if (this.wholeWords) {375this.wholeWords.checked = value;376}377}378379public getRegex(): boolean {380return this.regex?.checked ?? false;381}382383public setRegex(value: boolean): void {384if (this.regex) {385this.regex.checked = value;386this.validate();387}388}389390public focusOnCaseSensitive(): void {391this.caseSensitive?.focus();392}393394public focusOnRegex(): void {395this.regex?.focus();396}397398private _lastHighlightFindOptions: number = 0;399public highlightFindOptions(): void {400this.domNode.classList.remove('highlight-' + (this._lastHighlightFindOptions));401this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions;402this.domNode.classList.add('highlight-' + (this._lastHighlightFindOptions));403}404405public validate(): void {406this.inputBox.validate();407}408409public showMessage(message: InputBoxMessage): void {410this.inputBox.showMessage(message);411}412413public clearMessage(): void {414this.inputBox.hideMessage();415}416417private clearValidation(): void {418this.inputBox.hideMessage();419}420}421422423