Path: blob/main/src/vs/base/browser/ui/findinput/findInput.ts
3296 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 './findInput.css';16import * as nls from '../../../../nls.js';17import { DisposableStore, MutableDisposable } from '../../../common/lifecycle.js';18import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js';19import { IHistory } from '../../../common/history.js';202122export interface IFindInputOptions {23readonly placeholder?: string;24readonly width?: number;25readonly validation?: IInputValidator;26readonly label: string;27readonly flexibleHeight?: boolean;28readonly flexibleWidth?: boolean;29readonly flexibleMaxHeight?: number;3031readonly showCommonFindToggles?: boolean;32readonly appendCaseSensitiveLabel?: string;33readonly appendWholeWordsLabel?: string;34readonly appendRegexLabel?: string;35readonly additionalToggles?: Toggle[];36readonly showHistoryHint?: () => boolean;37readonly toggleStyles: IToggleStyles;38readonly inputBoxStyles: IInputBoxStyles;39readonly history?: IHistory<string>;40}4142const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input");4344export class FindInput extends Widget {4546static readonly OPTION_CHANGE: string = 'optionChange';4748private placeholder: string;49private validation?: IInputValidator;50private label: string;51private readonly showCommonFindToggles: boolean;52private fixFocusOnOptionClickEnabled = true;53private imeSessionInProgress = false;54private readonly additionalTogglesDisposables: MutableDisposable<DisposableStore> = this._register(new MutableDisposable());5556protected readonly controls: HTMLDivElement;57protected readonly regex?: RegexToggle;58protected readonly wholeWords?: WholeWordsToggle;59protected readonly caseSensitive?: CaseSensitiveToggle;60protected additionalToggles: Toggle[] = [];61public readonly domNode: HTMLElement;62public readonly inputBox: HistoryInputBox;6364private readonly _onDidOptionChange = this._register(new Emitter<boolean>());65public get onDidOptionChange(): Event<boolean /* via keyboard */> { return this._onDidOptionChange.event; }6667private readonly _onKeyDown = this._register(new Emitter<IKeyboardEvent>());68public get onKeyDown(): Event<IKeyboardEvent> { return this._onKeyDown.event; }6970private readonly _onMouseDown = this._register(new Emitter<IMouseEvent>());71public get onMouseDown(): Event<IMouseEvent> { return this._onMouseDown.event; }7273private readonly _onInput = this._register(new Emitter<void>());74public get onInput(): Event<void> { return this._onInput.event; }7576private readonly _onKeyUp = this._register(new Emitter<IKeyboardEvent>());77public get onKeyUp(): Event<IKeyboardEvent> { return this._onKeyUp.event; }7879private _onCaseSensitiveKeyDown = this._register(new Emitter<IKeyboardEvent>());80public get onCaseSensitiveKeyDown(): Event<IKeyboardEvent> { return this._onCaseSensitiveKeyDown.event; }8182private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>());83public get onRegexKeyDown(): Event<IKeyboardEvent> { return this._onRegexKeyDown.event; }8485constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IFindInputOptions) {86super();87this.placeholder = options.placeholder || '';88this.validation = options.validation;89this.label = options.label || NLS_DEFAULT_LABEL;90this.showCommonFindToggles = !!options.showCommonFindToggles;9192const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || '';93const appendWholeWordsLabel = options.appendWholeWordsLabel || '';94const appendRegexLabel = options.appendRegexLabel || '';95const flexibleHeight = !!options.flexibleHeight;96const flexibleWidth = !!options.flexibleWidth;97const flexibleMaxHeight = options.flexibleMaxHeight;9899this.domNode = document.createElement('div');100this.domNode.classList.add('monaco-findInput');101102this.inputBox = this._register(new HistoryInputBox(this.domNode, contextViewProvider, {103placeholder: this.placeholder || '',104ariaLabel: this.label || '',105validationOptions: {106validation: this.validation107},108showHistoryHint: options.showHistoryHint,109flexibleHeight,110flexibleWidth,111flexibleMaxHeight,112inputBoxStyles: options.inputBoxStyles,113history: options.history114}));115116const hoverDelegate = this._register(createInstantHoverDelegate());117118if (this.showCommonFindToggles) {119this.regex = this._register(new RegexToggle({120appendTitle: appendRegexLabel,121isChecked: false,122hoverDelegate,123...options.toggleStyles124}));125this._register(this.regex.onChange(viaKeyboard => {126this._onDidOptionChange.fire(viaKeyboard);127if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {128this.inputBox.focus();129}130this.validate();131}));132this._register(this.regex.onKeyDown(e => {133this._onRegexKeyDown.fire(e);134}));135136this.wholeWords = this._register(new WholeWordsToggle({137appendTitle: appendWholeWordsLabel,138isChecked: false,139hoverDelegate,140...options.toggleStyles141}));142this._register(this.wholeWords.onChange(viaKeyboard => {143this._onDidOptionChange.fire(viaKeyboard);144if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {145this.inputBox.focus();146}147this.validate();148}));149150this.caseSensitive = this._register(new CaseSensitiveToggle({151appendTitle: appendCaseSensitiveLabel,152isChecked: false,153hoverDelegate,154...options.toggleStyles155}));156this._register(this.caseSensitive.onChange(viaKeyboard => {157this._onDidOptionChange.fire(viaKeyboard);158if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {159this.inputBox.focus();160}161this.validate();162}));163this._register(this.caseSensitive.onKeyDown(e => {164this._onCaseSensitiveKeyDown.fire(e);165}));166167// Arrow-Key support to navigate between options168const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];169this.onkeydown(this.domNode, (event: IKeyboardEvent) => {170if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {171const index = indexes.indexOf(<HTMLElement>this.domNode.ownerDocument.activeElement);172if (index >= 0) {173let newIndex: number = -1;174if (event.equals(KeyCode.RightArrow)) {175newIndex = (index + 1) % indexes.length;176} else if (event.equals(KeyCode.LeftArrow)) {177if (index === 0) {178newIndex = indexes.length - 1;179} else {180newIndex = index - 1;181}182}183184if (event.equals(KeyCode.Escape)) {185indexes[index].blur();186this.inputBox.focus();187} else if (newIndex >= 0) {188indexes[newIndex].focus();189}190191dom.EventHelper.stop(event, true);192}193}194});195}196197this.controls = document.createElement('div');198this.controls.className = 'controls';199this.controls.style.display = this.showCommonFindToggles ? '' : 'none';200if (this.caseSensitive) {201this.controls.append(this.caseSensitive.domNode);202}203if (this.wholeWords) {204this.controls.appendChild(this.wholeWords.domNode);205}206if (this.regex) {207this.controls.appendChild(this.regex.domNode);208}209210this.setAdditionalToggles(options?.additionalToggles);211212if (this.controls) {213this.domNode.appendChild(this.controls);214}215216parent?.appendChild(this.domNode);217218this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionstart', (e: CompositionEvent) => {219this.imeSessionInProgress = true;220}));221this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionend', (e: CompositionEvent) => {222this.imeSessionInProgress = false;223this._onInput.fire();224}));225226this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e));227this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e));228this.oninput(this.inputBox.inputElement, (e) => this._onInput.fire());229this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e));230}231232public get isImeSessionInProgress(): boolean {233return this.imeSessionInProgress;234}235236public get onDidChange(): Event<string> {237return this.inputBox.onDidChange;238}239240public layout(style: { collapsedFindWidget: boolean; narrowFindWidget: boolean; reducedFindWidget: boolean }) {241this.inputBox.layout();242this.updateInputBoxPadding(style.collapsedFindWidget);243}244245public enable(): void {246this.domNode.classList.remove('disabled');247this.inputBox.enable();248this.regex?.enable();249this.wholeWords?.enable();250this.caseSensitive?.enable();251252for (const toggle of this.additionalToggles) {253toggle.enable();254}255}256257public disable(): void {258this.domNode.classList.add('disabled');259this.inputBox.disable();260this.regex?.disable();261this.wholeWords?.disable();262this.caseSensitive?.disable();263264for (const toggle of this.additionalToggles) {265toggle.disable();266}267}268269public setFocusInputOnOptionClick(value: boolean): void {270this.fixFocusOnOptionClickEnabled = value;271}272273public setEnabled(enabled: boolean): void {274if (enabled) {275this.enable();276} else {277this.disable();278}279}280281public setAdditionalToggles(toggles: Toggle[] | undefined): void {282for (const currentToggle of this.additionalToggles) {283currentToggle.domNode.remove();284}285this.additionalToggles = [];286this.additionalTogglesDisposables.value = new DisposableStore();287288for (const toggle of toggles ?? []) {289this.additionalTogglesDisposables.value.add(toggle);290this.controls.appendChild(toggle.domNode);291292this.additionalTogglesDisposables.value.add(toggle.onChange(viaKeyboard => {293this._onDidOptionChange.fire(viaKeyboard);294if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {295this.inputBox.focus();296}297}));298299this.additionalToggles.push(toggle);300}301302if (this.additionalToggles.length > 0) {303this.controls.style.display = '';304}305306this.updateInputBoxPadding();307}308309private updateInputBoxPadding(controlsHidden = false) {310if (controlsHidden) {311this.inputBox.paddingRight = 0;312} else {313this.inputBox.paddingRight =314((this.caseSensitive?.width() ?? 0) + (this.wholeWords?.width() ?? 0) + (this.regex?.width() ?? 0))315+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);316}317}318319public clear(): void {320this.clearValidation();321this.setValue('');322this.focus();323}324325public getValue(): string {326return this.inputBox.value;327}328329public setValue(value: string): void {330if (this.inputBox.value !== value) {331this.inputBox.value = value;332}333}334335public onSearchSubmit(): void {336this.inputBox.addToHistory();337}338339public select(): void {340this.inputBox.select();341}342343public focus(): void {344this.inputBox.focus();345}346347public getCaseSensitive(): boolean {348return this.caseSensitive?.checked ?? false;349}350351public setCaseSensitive(value: boolean): void {352if (this.caseSensitive) {353this.caseSensitive.checked = value;354}355}356357public getWholeWords(): boolean {358return this.wholeWords?.checked ?? false;359}360361public setWholeWords(value: boolean): void {362if (this.wholeWords) {363this.wholeWords.checked = value;364}365}366367public getRegex(): boolean {368return this.regex?.checked ?? false;369}370371public setRegex(value: boolean): void {372if (this.regex) {373this.regex.checked = value;374this.validate();375}376}377378public focusOnCaseSensitive(): void {379this.caseSensitive?.focus();380}381382public focusOnRegex(): void {383this.regex?.focus();384}385386private _lastHighlightFindOptions: number = 0;387public highlightFindOptions(): void {388this.domNode.classList.remove('highlight-' + (this._lastHighlightFindOptions));389this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions;390this.domNode.classList.add('highlight-' + (this._lastHighlightFindOptions));391}392393public validate(): void {394this.inputBox.validate();395}396397public showMessage(message: InputBoxMessage): void {398this.inputBox.showMessage(message);399}400401public clearMessage(): void {402this.inputBox.hideMessage();403}404405private clearValidation(): void {406this.inputBox.hideMessage();407}408}409410411