Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/findinput/findInput.ts
5240 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../dom.js';
7
import { IKeyboardEvent } from '../../keyboardEvent.js';
8
import { IMouseEvent } from '../../mouseEvent.js';
9
import { IToggleStyles, Toggle } from '../toggle/toggle.js';
10
import { IContextViewProvider } from '../contextview/contextview.js';
11
import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from './findInputToggles.js';
12
import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from '../inputbox/inputBox.js';
13
import { Widget } from '../widget.js';
14
import { Emitter, Event } from '../../../common/event.js';
15
import { KeyCode } from '../../../common/keyCodes.js';
16
import { IAction } from '../../../common/actions.js';
17
import type { IActionViewItemProvider } from '../actionbar/actionbar.js';
18
import './findInput.css';
19
import * as nls from '../../../../nls.js';
20
import { DisposableStore, MutableDisposable } from '../../../common/lifecycle.js';
21
import { IHistory } from '../../../common/history.js';
22
import type { IHoverLifecycleOptions } from '../hover/hover.js';
23
24
25
export interface IFindInputOptions {
26
readonly placeholder?: string;
27
readonly width?: number;
28
readonly validation?: IInputValidator;
29
readonly label: string;
30
readonly flexibleHeight?: boolean;
31
readonly flexibleWidth?: boolean;
32
readonly flexibleMaxHeight?: number;
33
34
readonly showCommonFindToggles?: boolean;
35
readonly appendCaseSensitiveLabel?: string;
36
readonly appendWholeWordsLabel?: string;
37
readonly appendRegexLabel?: string;
38
readonly additionalToggles?: Toggle[];
39
readonly actions?: ReadonlyArray<IAction>;
40
readonly actionViewItemProvider?: IActionViewItemProvider;
41
readonly showHistoryHint?: () => boolean;
42
readonly toggleStyles: IToggleStyles;
43
readonly inputBoxStyles: IInputBoxStyles;
44
readonly history?: IHistory<string>;
45
readonly hoverLifecycleOptions?: IHoverLifecycleOptions;
46
readonly hideHoverOnValueChange?: boolean;
47
}
48
49
const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input");
50
51
export class FindInput extends Widget {
52
53
static readonly OPTION_CHANGE: string = 'optionChange';
54
55
private placeholder: string;
56
private validation?: IInputValidator;
57
private label: string;
58
private readonly showCommonFindToggles: boolean;
59
private fixFocusOnOptionClickEnabled = true;
60
private imeSessionInProgress = false;
61
private readonly additionalTogglesDisposables: MutableDisposable<DisposableStore> = this._register(new MutableDisposable());
62
63
protected readonly controls: HTMLDivElement;
64
protected readonly regex?: RegexToggle;
65
protected readonly wholeWords?: WholeWordsToggle;
66
protected readonly caseSensitive?: CaseSensitiveToggle;
67
protected additionalToggles: Toggle[] = [];
68
public readonly domNode: HTMLElement;
69
public readonly inputBox: HistoryInputBox;
70
71
private readonly _onDidOptionChange = this._register(new Emitter<boolean>());
72
public get onDidOptionChange(): Event<boolean /* via keyboard */> { return this._onDidOptionChange.event; }
73
74
private readonly _onKeyDown = this._register(new Emitter<IKeyboardEvent>());
75
public get onKeyDown(): Event<IKeyboardEvent> { return this._onKeyDown.event; }
76
77
private readonly _onMouseDown = this._register(new Emitter<IMouseEvent>());
78
public get onMouseDown(): Event<IMouseEvent> { return this._onMouseDown.event; }
79
80
private readonly _onInput = this._register(new Emitter<void>());
81
public get onInput(): Event<void> { return this._onInput.event; }
82
83
private readonly _onKeyUp = this._register(new Emitter<IKeyboardEvent>());
84
public get onKeyUp(): Event<IKeyboardEvent> { return this._onKeyUp.event; }
85
86
private _onCaseSensitiveKeyDown = this._register(new Emitter<IKeyboardEvent>());
87
public get onCaseSensitiveKeyDown(): Event<IKeyboardEvent> { return this._onCaseSensitiveKeyDown.event; }
88
89
private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>());
90
public get onRegexKeyDown(): Event<IKeyboardEvent> { return this._onRegexKeyDown.event; }
91
92
constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IFindInputOptions) {
93
super();
94
this.placeholder = options.placeholder || '';
95
this.validation = options.validation;
96
this.label = options.label || NLS_DEFAULT_LABEL;
97
this.showCommonFindToggles = !!options.showCommonFindToggles;
98
99
const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || '';
100
const appendWholeWordsLabel = options.appendWholeWordsLabel || '';
101
const appendRegexLabel = options.appendRegexLabel || '';
102
const flexibleHeight = !!options.flexibleHeight;
103
const flexibleWidth = !!options.flexibleWidth;
104
const flexibleMaxHeight = options.flexibleMaxHeight;
105
106
this.domNode = document.createElement('div');
107
this.domNode.classList.add('monaco-findInput');
108
109
this.inputBox = this._register(new HistoryInputBox(this.domNode, contextViewProvider, {
110
placeholder: this.placeholder || '',
111
ariaLabel: this.label || '',
112
validationOptions: {
113
validation: this.validation
114
},
115
showHistoryHint: options.showHistoryHint,
116
flexibleHeight,
117
flexibleWidth,
118
flexibleMaxHeight,
119
inputBoxStyles: options.inputBoxStyles,
120
history: options.history,
121
actions: options.actions,
122
actionViewItemProvider: options.actionViewItemProvider,
123
hideHoverOnValueChange: options.hideHoverOnValueChange
124
}));
125
126
if (this.showCommonFindToggles) {
127
const hoverLifecycleOptions: IHoverLifecycleOptions = options?.hoverLifecycleOptions || { groupId: 'find-input' };
128
this.regex = this._register(new RegexToggle({
129
appendTitle: appendRegexLabel,
130
isChecked: false,
131
hoverLifecycleOptions,
132
...options.toggleStyles
133
}));
134
this._register(this.regex.onChange(viaKeyboard => {
135
this._onDidOptionChange.fire(viaKeyboard);
136
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
137
this.inputBox.focus();
138
}
139
this.validate();
140
}));
141
this._register(this.regex.onKeyDown(e => {
142
this._onRegexKeyDown.fire(e);
143
}));
144
145
this.wholeWords = this._register(new WholeWordsToggle({
146
appendTitle: appendWholeWordsLabel,
147
isChecked: false,
148
hoverLifecycleOptions,
149
...options.toggleStyles
150
}));
151
this._register(this.wholeWords.onChange(viaKeyboard => {
152
this._onDidOptionChange.fire(viaKeyboard);
153
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
154
this.inputBox.focus();
155
}
156
this.validate();
157
}));
158
159
this.caseSensitive = this._register(new CaseSensitiveToggle({
160
appendTitle: appendCaseSensitiveLabel,
161
isChecked: false,
162
hoverLifecycleOptions,
163
...options.toggleStyles
164
}));
165
this._register(this.caseSensitive.onChange(viaKeyboard => {
166
this._onDidOptionChange.fire(viaKeyboard);
167
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
168
this.inputBox.focus();
169
}
170
this.validate();
171
}));
172
this._register(this.caseSensitive.onKeyDown(e => {
173
this._onCaseSensitiveKeyDown.fire(e);
174
}));
175
176
// Arrow-Key support to navigate between options
177
const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];
178
this.onkeydown(this.domNode, (event: IKeyboardEvent) => {
179
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {
180
const index = indexes.indexOf(<HTMLElement>this.domNode.ownerDocument.activeElement);
181
if (index >= 0) {
182
let newIndex: number = -1;
183
if (event.equals(KeyCode.RightArrow)) {
184
newIndex = (index + 1) % indexes.length;
185
} else if (event.equals(KeyCode.LeftArrow)) {
186
if (index === 0) {
187
newIndex = indexes.length - 1;
188
} else {
189
newIndex = index - 1;
190
}
191
}
192
193
if (event.equals(KeyCode.Escape)) {
194
indexes[index].blur();
195
this.inputBox.focus();
196
} else if (newIndex >= 0) {
197
indexes[newIndex].focus();
198
}
199
200
dom.EventHelper.stop(event, true);
201
}
202
}
203
});
204
}
205
206
this.controls = document.createElement('div');
207
this.controls.className = 'controls';
208
this.controls.style.display = this.showCommonFindToggles ? '' : 'none';
209
if (this.caseSensitive) {
210
this.controls.append(this.caseSensitive.domNode);
211
}
212
if (this.wholeWords) {
213
this.controls.appendChild(this.wholeWords.domNode);
214
}
215
if (this.regex) {
216
this.controls.appendChild(this.regex.domNode);
217
}
218
219
this.setAdditionalToggles(options?.additionalToggles);
220
221
if (this.controls) {
222
this.domNode.appendChild(this.controls);
223
}
224
225
parent?.appendChild(this.domNode);
226
227
this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionstart', (e: CompositionEvent) => {
228
this.imeSessionInProgress = true;
229
}));
230
this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionend', (e: CompositionEvent) => {
231
this.imeSessionInProgress = false;
232
this._onInput.fire();
233
}));
234
235
this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e));
236
this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e));
237
this.oninput(this.inputBox.inputElement, (e) => this._onInput.fire());
238
this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e));
239
}
240
241
public get isImeSessionInProgress(): boolean {
242
return this.imeSessionInProgress;
243
}
244
245
public get onDidChange(): Event<string> {
246
return this.inputBox.onDidChange;
247
}
248
249
public layout(style: { collapsedFindWidget: boolean; narrowFindWidget: boolean; reducedFindWidget: boolean }) {
250
this.inputBox.layout();
251
this.updateInputBoxPadding(style.collapsedFindWidget);
252
}
253
254
public enable(): void {
255
this.domNode.classList.remove('disabled');
256
this.inputBox.enable();
257
this.regex?.enable();
258
this.wholeWords?.enable();
259
this.caseSensitive?.enable();
260
261
for (const toggle of this.additionalToggles) {
262
toggle.enable();
263
}
264
}
265
266
public disable(): void {
267
this.domNode.classList.add('disabled');
268
this.inputBox.disable();
269
this.regex?.disable();
270
this.wholeWords?.disable();
271
this.caseSensitive?.disable();
272
273
for (const toggle of this.additionalToggles) {
274
toggle.disable();
275
}
276
}
277
278
public setFocusInputOnOptionClick(value: boolean): void {
279
this.fixFocusOnOptionClickEnabled = value;
280
}
281
282
public setEnabled(enabled: boolean): void {
283
if (enabled) {
284
this.enable();
285
} else {
286
this.disable();
287
}
288
}
289
290
public setAdditionalToggles(toggles: Toggle[] | undefined): void {
291
for (const currentToggle of this.additionalToggles) {
292
currentToggle.domNode.remove();
293
}
294
this.additionalToggles = [];
295
this.additionalTogglesDisposables.value = new DisposableStore();
296
297
for (const toggle of toggles ?? []) {
298
this.additionalTogglesDisposables.value.add(toggle);
299
this.controls.appendChild(toggle.domNode);
300
301
this.additionalTogglesDisposables.value.add(toggle.onChange(viaKeyboard => {
302
this._onDidOptionChange.fire(viaKeyboard);
303
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
304
this.inputBox.focus();
305
}
306
}));
307
308
this.additionalToggles.push(toggle);
309
}
310
311
if (this.additionalToggles.length > 0) {
312
this.controls.style.display = '';
313
}
314
315
this.updateInputBoxPadding();
316
}
317
318
public setActions(actions: ReadonlyArray<IAction> | undefined, actionViewItemProvider?: IActionViewItemProvider): void {
319
this.inputBox.setActions(actions, actionViewItemProvider);
320
}
321
322
private updateInputBoxPadding(controlsHidden = false) {
323
if (controlsHidden) {
324
this.inputBox.paddingRight = 0;
325
} else {
326
this.inputBox.paddingRight =
327
((this.caseSensitive?.width() ?? 0) + (this.wholeWords?.width() ?? 0) + (this.regex?.width() ?? 0))
328
+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);
329
}
330
}
331
332
public clear(): void {
333
this.clearValidation();
334
this.setValue('');
335
this.focus();
336
}
337
338
public getValue(): string {
339
return this.inputBox.value;
340
}
341
342
public setValue(value: string): void {
343
if (this.inputBox.value !== value) {
344
this.inputBox.value = value;
345
}
346
}
347
348
public onSearchSubmit(): void {
349
this.inputBox.addToHistory();
350
}
351
352
public select(): void {
353
this.inputBox.select();
354
}
355
356
public focus(): void {
357
this.inputBox.focus();
358
}
359
360
public getCaseSensitive(): boolean {
361
return this.caseSensitive?.checked ?? false;
362
}
363
364
public setCaseSensitive(value: boolean): void {
365
if (this.caseSensitive) {
366
this.caseSensitive.checked = value;
367
}
368
}
369
370
public getWholeWords(): boolean {
371
return this.wholeWords?.checked ?? false;
372
}
373
374
public setWholeWords(value: boolean): void {
375
if (this.wholeWords) {
376
this.wholeWords.checked = value;
377
}
378
}
379
380
public getRegex(): boolean {
381
return this.regex?.checked ?? false;
382
}
383
384
public setRegex(value: boolean): void {
385
if (this.regex) {
386
this.regex.checked = value;
387
this.validate();
388
}
389
}
390
391
public focusOnCaseSensitive(): void {
392
this.caseSensitive?.focus();
393
}
394
395
public focusOnRegex(): void {
396
this.regex?.focus();
397
}
398
399
private _lastHighlightFindOptions: number = 0;
400
public highlightFindOptions(): void {
401
this.domNode.classList.remove('highlight-' + (this._lastHighlightFindOptions));
402
this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions;
403
this.domNode.classList.add('highlight-' + (this._lastHighlightFindOptions));
404
}
405
406
public validate(): void {
407
this.inputBox.validate();
408
}
409
410
public showMessage(message: InputBoxMessage): void {
411
this.inputBox.showMessage(message);
412
}
413
414
public clearMessage(): void {
415
this.inputBox.hideMessage();
416
}
417
418
private clearValidation(): void {
419
this.inputBox.hideMessage();
420
}
421
}
422
423