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