searchWidget.ts 23.0 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as dom from 'vs/base/browser/dom';
7
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
S
Sandeep Somavarapu 已提交
8
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
9
import { Button, IButtonOptions } from 'vs/base/browser/ui/button/button';
10
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
J
jeanp413 已提交
11 12
import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput';
import { IMessage } from 'vs/base/browser/ui/inputbox/inputBox';
13 14 15 16
import { Widget } from 'vs/base/browser/ui/widget';
import { Action } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
17
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
18
import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel';
19
import * as nls from 'vs/nls';
20 21
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
22 23 24 25
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
26
import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
J
jeanp413 已提交
27
import { attachFindReplaceInputBoxStyler } from 'vs/platform/theme/common/styler';
28
import { IThemeService } from 'vs/platform/theme/common/themeService';
J
jeanp413 已提交
29
import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget';
30 31
import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
32 33 34
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
35
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
36
import { isMacintosh } from 'vs/base/common/platform';
37

38 39
/** Specified in searchview.css */
export const SingleLineInputHeight = 24;
R
romainHainaut 已提交
40

41
export interface ISearchWidgetOptions {
J
Johannes Rieken 已提交
42
	value?: string;
43
	replaceValue?: string;
J
Johannes Rieken 已提交
44 45 46
	isRegex?: boolean;
	isCaseSensitive?: boolean;
	isWholeWords?: boolean;
47 48
	searchHistory?: string[];
	replaceHistory?: string[];
49
	preserveCase?: boolean;
50 51
}

52 53
class ReplaceAllAction extends Action {

54
	private static fgInstance: ReplaceAllAction | null = null;
R
Rob Lourens 已提交
55
	static readonly ID: string = 'search.action.replaceAll';
56

J
Johannes Rieken 已提交
57
	static get INSTANCE(): ReplaceAllAction {
58
		if (ReplaceAllAction.fgInstance === null) {
J
Johannes Rieken 已提交
59
			ReplaceAllAction.fgInstance = new ReplaceAllAction();
60 61 62 63
		}
		return ReplaceAllAction.fgInstance;
	}

64
	private _searchWidget: SearchWidget | null = null;
65 66

	constructor() {
67
		super(ReplaceAllAction.ID, '', 'codicon-replace-all', false);
68 69 70
	}

	set searchWidget(searchWidget: SearchWidget) {
J
Johannes Rieken 已提交
71
		this._searchWidget = searchWidget;
72 73
	}

74
	run(): Promise<any> {
75 76 77
		if (this._searchWidget) {
			return this._searchWidget.triggerReplaceAll();
		}
78
		return Promise.resolve(null);
79 80 81
	}
}

82 83
const ctrlKeyMod = (isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd);

J
jeanp413 已提交
84 85
function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {
	const isMultiline = !!value.match(/\n/);
86
	if (textarea && (isMultiline || textarea.clientHeight > SingleLineInputHeight) && textarea.selectionStart > 0) {
J
jeanp413 已提交
87 88 89 90 91 92 93
		event.stopPropagation();
		return;
	}
}

function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {
	const isMultiline = !!value.match(/\n/);
94
	if (textarea && (isMultiline || textarea.clientHeight > SingleLineInputHeight) && textarea.selectionEnd < textarea.value.length) {
J
jeanp413 已提交
95 96 97 98 99
		event.stopPropagation();
		return;
	}
}

100 101
export class SearchWidget extends Widget {

102 103
	private static readonly REPLACE_ALL_DISABLED_LABEL = nls.localize('search.action.replaceAll.disabled.label', "Replace All (Submit Search to Enable)");
	private static readonly REPLACE_ALL_ENABLED_LABEL = (keyBindingService2: IKeybindingService): string => {
104
		const kb = keyBindingService2.lookupKeybinding(ReplaceAllAction.ID);
B
Benjamin Pasero 已提交
105
		return appendKeyBindingLabel(nls.localize('search.action.replaceAll.enabled.label', "Replace All"), kb, keyBindingService2);
106
	};
S
Sandeep Somavarapu 已提交
107

108
	domNode!: HTMLElement;
109

110 111
	searchInput!: FindInput;
	searchInputFocusTracker!: dom.IFocusTracker;
112
	private searchInputBoxFocused: IContextKey<boolean>;
113

114
	private replaceContainer!: HTMLElement;
J
jeanp413 已提交
115 116 117
	replaceInput!: ReplaceInput;
	replaceInputFocusTracker!: dom.IFocusTracker;
	private replaceInputBoxFocused: IContextKey<boolean>;
118 119
	private toggleReplaceButton!: Button;
	private replaceAllAction!: ReplaceAllAction;
A
Alex Dima 已提交
120
	private replaceActive: IContextKey<boolean>;
121
	private replaceActionBar!: ActionBar;
S
Sandeep Somavarapu 已提交
122
	private _replaceHistoryDelayer: Delayer<void>;
123
	private _searchDelayer: Delayer<void>;
124
	private ignoreGlobalFindBufferOnNextFocus = false;
125
	private previousGlobalFindBufferValue: string | null = null;
126

127 128
	private _onSearchSubmit = this._register(new Emitter<boolean>());
	readonly onSearchSubmit: Event<boolean /* triggeredOnType */> = this._onSearchSubmit.event;
129

130 131
	private _onSearchCancel = this._register(new Emitter<{ focus: boolean }>());
	readonly onSearchCancel: Event<{ focus: boolean }> = this._onSearchCancel.event;
132 133

	private _onReplaceToggled = this._register(new Emitter<void>());
R
Rob Lourens 已提交
134
	readonly onReplaceToggled: Event<void> = this._onReplaceToggled.event;
135

S
Sandeep Somavarapu 已提交
136
	private _onReplaceStateChange = this._register(new Emitter<boolean>());
R
Rob Lourens 已提交
137
	readonly onReplaceStateChange: Event<boolean> = this._onReplaceStateChange.event;
138

139 140 141
	private _onPreserveCaseChange = this._register(new Emitter<boolean>());
	readonly onPreserveCaseChange: Event<boolean> = this._onPreserveCaseChange.event;

U
Ubuntu 已提交
142 143
	private _onReplaceValueChanged = this._register(new Emitter<void>());
	readonly onReplaceValueChanged: Event<void> = this._onReplaceValueChanged.event;
144 145

	private _onReplaceAll = this._register(new Emitter<void>());
R
Rob Lourens 已提交
146
	readonly onReplaceAll: Event<void> = this._onReplaceAll.event;
147

S
Sandeep Somavarapu 已提交
148
	private _onBlur = this._register(new Emitter<void>());
R
Rob Lourens 已提交
149
	readonly onBlur: Event<void> = this._onBlur.event;
S
Sandeep Somavarapu 已提交
150

151
	private _onDidHeightChange = this._register(new Emitter<void>());
R
Rob Lourens 已提交
152
	readonly onDidHeightChange: Event<void> = this._onDidHeightChange.event;
153

154 155
	private temporarilySkipSearchOnChange = false;

156
	constructor(
157
		container: HTMLElement,
158
		options: ISearchWidgetOptions,
159 160 161 162 163
		@IContextViewService private readonly contextViewService: IContextViewService,
		@IThemeService private readonly themeService: IThemeService,
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IKeybindingService private readonly keyBindingService: IKeybindingService,
		@IClipboardService private readonly clipboardServce: IClipboardService,
164 165
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IAccessibilityService private readonly accessibilityService: IAccessibilityService
166
	) {
167
		super();
S
Sandeep Somavarapu 已提交
168 169 170
		this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService);
		this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService);
		this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService);
S
Sandeep Somavarapu 已提交
171
		this._replaceHistoryDelayer = new Delayer<void>(500);
172
		this._searchDelayer = this._register(new Delayer<void>(this.searchConfiguration.searchOnTypeDebouncePeriod));
173
		this.render(container, options);
174 175 176 177 178 179

		this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('editor.accessibilitySupport')) {
				this.updateAccessibilitySupport();
			}
		});
180
		this.accessibilityService.onDidChangeAccessibilitySupport(() => this.updateAccessibilitySupport());
181
		this.updateAccessibilitySupport();
182 183
	}

R
Rob Lourens 已提交
184
	focus(select: boolean = true, focusReplace: boolean = false, suppressGlobalSearchBuffer = false): void {
185 186
		this.ignoreGlobalFindBufferOnNextFocus = suppressGlobalSearchBuffer;

187 188 189 190 191 192 193 194 195 196 197 198 199
		if (focusReplace && this.isReplaceShown()) {
			this.replaceInput.focus();
			if (select) {
				this.replaceInput.select();
			}
		} else {
			this.searchInput.focus();
			if (select) {
				this.searchInput.select();
			}
		}
	}

R
Rob Lourens 已提交
200
	setWidth(width: number) {
201
		this.searchInput.inputBox.layout();
J
Johannes Rieken 已提交
202
		this.replaceInput.width = width - 28;
J
jeanp413 已提交
203
		this.replaceInput.inputBox.layout();
204 205
	}

R
Rob Lourens 已提交
206
	clear() {
207
		this.searchInput.clear();
J
jeanp413 已提交
208
		this.replaceInput.setValue('');
S
Sandeep Somavarapu 已提交
209
		this.setReplaceAllActionState(false);
210 211
	}

R
Rob Lourens 已提交
212
	isReplaceShown(): boolean {
S
Sandeep Somavarapu 已提交
213
		return !dom.hasClass(this.replaceContainer, 'disabled');
214 215
	}

S
Sandeep Somavarapu 已提交
216
	isReplaceActive(): boolean {
217
		return !!this.replaceActive.get();
S
Sandeep Somavarapu 已提交
218 219
	}

R
Rob Lourens 已提交
220
	getReplaceValue(): string {
J
jeanp413 已提交
221
		return this.replaceInput.getValue();
222 223
	}

R
Rob Lourens 已提交
224
	toggleReplace(show?: boolean): void {
R
Rob Lourens 已提交
225
		if (show === undefined || show !== this.isReplaceShown()) {
226
			this.onToggleReplaceButton();
S
Sandeep Somavarapu 已提交
227 228 229
		}
	}

R
Rob Lourens 已提交
230
	getSearchHistory(): string[] {
231
		return this.searchInput.inputBox.getHistory();
232 233
	}

R
Rob Lourens 已提交
234
	getReplaceHistory(): string[] {
J
jeanp413 已提交
235
		return this.replaceInput.inputBox.getHistory();
236 237
	}

R
Rob Lourens 已提交
238
	clearHistory(): void {
239
		this.searchInput.inputBox.clearHistory();
240 241
	}

R
Rob Lourens 已提交
242
	showNextSearchTerm() {
243
		this.searchInput.inputBox.showNextValue();
244 245
	}

R
Rob Lourens 已提交
246
	showPreviousSearchTerm() {
247
		this.searchInput.inputBox.showPreviousValue();
248 249
	}

R
Rob Lourens 已提交
250
	showNextReplaceTerm() {
J
jeanp413 已提交
251
		this.replaceInput.inputBox.showNextValue();
252 253
	}

R
Rob Lourens 已提交
254
	showPreviousReplaceTerm() {
J
jeanp413 已提交
255
		this.replaceInput.inputBox.showPreviousValue();
256 257
	}

R
Rob Lourens 已提交
258
	searchInputHasFocus(): boolean {
M
Matt Bierner 已提交
259
		return !!this.searchInputBoxFocused.get();
260 261
	}

R
Rob Lourens 已提交
262
	replaceInputHasFocus(): boolean {
J
jeanp413 已提交
263
		return this.replaceInput.inputBox.hasFocus();
264 265
	}

R
Rob Lourens 已提交
266
	focusReplaceAllAction(): void {
S
Sandeep Somavarapu 已提交
267 268 269
		this.replaceActionBar.focus(true);
	}

R
Rob Lourens 已提交
270
	focusRegexAction(): void {
S
Sandeep Somavarapu 已提交
271 272 273
		this.searchInput.focusOnRegex();
	}

274 275 276 277
	private render(container: HTMLElement, options: ISearchWidgetOptions): void {
		this.domNode = dom.append(container, dom.$('.search-widget'));
		this.domNode.style.position = 'relative';

278 279 280
		this.renderToggleReplaceButton(this.domNode);

		this.renderSearchInput(this.domNode, options);
281
		this.renderReplaceInput(this.domNode, options);
282 283
	}

284
	private isScreenReaderOptimized() {
285
		const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled;
286 287 288 289
		const config = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
		return config === 'on' || (config === 'auto' && detected);
	}

290
	private updateAccessibilitySupport(): void {
291
		this.searchInput.setFocusInputOnOptionClick(!this.isScreenReaderOptimized());
292 293
	}

294
	private renderToggleReplaceButton(parent: HTMLElement): void {
295
		const opts: IButtonOptions = {
M
Matt Bierner 已提交
296 297 298 299
			buttonBackground: undefined,
			buttonBorder: undefined,
			buttonForeground: undefined,
			buttonHoverBackground: undefined
300 301
		};
		this.toggleReplaceButton = this._register(new Button(parent, opts));
302
		this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false');
303 304
		this.toggleReplaceButton.element.classList.add('codicon');
		this.toggleReplaceButton.element.classList.add('codicon-chevron-right');
R
Rob Lourens 已提交
305
		this.toggleReplaceButton.icon = 'toggle-replace-button';
J
Joao Moreno 已提交
306 307
		// TODO@joh need to dispose this listener eventually
		this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton());
308
		this.toggleReplaceButton.element.title = nls.localize('search.replace.toggle.button.title', "Toggle Replace");
309 310 311
	}

	private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
312
		const inputOptions: IFindInputOptions = {
313
			label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search or Escape to cancel'),
314
			validation: (value: string) => this.validateSearchInput(value),
S
Sandeep Somavarapu 已提交
315
			placeholder: nls.localize('search.placeHolder', "Search"),
S
Sandeep Somavarapu 已提交
316 317 318
			appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keyBindingService),
			appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService),
			appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService),
319 320
			history: options.searchHistory,
			flexibleHeight: true
321 322
		};

323
		const searchInputContainer = dom.append(parent, dom.$('.search-container.input-box'));
324
		this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService, true));
J
jeanp413 已提交
325
		this._register(attachFindReplaceInputBoxStyler(this.searchInput, this.themeService));
S
Sandeep Somavarapu 已提交
326
		this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent));
327 328 329 330
		this.searchInput.setValue(options.value || '');
		this.searchInput.setRegex(!!options.isRegex);
		this.searchInput.setCaseSensitive(!!options.isCaseSensitive);
		this.searchInput.setWholeWords(!!options.isWholeWords);
331 332 333 334
		this._register(this.searchInput.onCaseSensitiveKeyDown((keyboardEvent: IKeyboardEvent) => this.onCaseSensitiveKeyDown(keyboardEvent)));
		this._register(this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent)));
		this._register(this.searchInput.inputBox.onDidChange(() => this.onSearchInputChanged()));
		this._register(this.searchInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire()));
335

336
		this._register(this.onReplaceValueChanged(() => {
J
jeanp413 已提交
337
			this._replaceHistoryDelayer.trigger(() => this.replaceInput.inputBox.addToHistory());
338 339
		}));

S
Sandeep Somavarapu 已提交
340
		this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement));
341 342 343
		this._register(this.searchInputFocusTracker.onDidFocus(() => {
			this.searchInputBoxFocused.set(true);

R
Rob Lourens 已提交
344
			const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard;
345 346 347
			if (!this.ignoreGlobalFindBufferOnNextFocus && useGlobalFindBuffer) {
				const globalBufferText = this.clipboardServce.readFindText();
				if (this.previousGlobalFindBufferValue !== globalBufferText) {
S
Sandeep Somavarapu 已提交
348
					this.searchInput.inputBox.addToHistory();
349 350 351 352 353 354 355 356 357
					this.searchInput.setValue(globalBufferText);
					this.searchInput.select();
				}

				this.previousGlobalFindBufferValue = globalBufferText;
			}

			this.ignoreGlobalFindBufferOnNextFocus = false;
		}));
358
		this._register(this.searchInputFocusTracker.onDidBlur(() => this.searchInputBoxFocused.set(false)));
359 360
	}

361
	private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
J
Joao Moreno 已提交
362
		this.replaceContainer = dom.append(parent, dom.$('.replace-container.disabled'));
363 364
		const replaceBox = dom.append(this.replaceContainer, dom.$('.replace-input'));

J
jeanp413 已提交
365 366
		this.replaceInput = this._register(new ContextScopedReplaceInput(replaceBox, this.contextViewService, {
			label: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'),
367
			placeholder: nls.localize('search.replace.placeHolder', "Replace"),
J
jeanp413 已提交
368
			history: options.replaceHistory,
369
			flexibleHeight: true
J
jeanp413 已提交
370
		}, this.contextKeyService, true));
371

J
jeanp413 已提交
372
		this._register(this.replaceInput.onDidOptionChange(viaKeyboard => {
373
			if (!viaKeyboard) {
J
jeanp413 已提交
374
				this._onPreserveCaseChange.fire(this.replaceInput.getPreserveCase());
375 376 377
			}
		}));

J
jeanp413 已提交
378 379 380 381 382
		this._register(attachFindReplaceInputBoxStyler(this.replaceInput, this.themeService));
		this.replaceInput.onKeyDown((keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent));
		this.replaceInput.setValue(options.replaceValue || '');
		this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire()));
		this._register(this.replaceInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire()));
S
Sandeep Somavarapu 已提交
383 384

		this.replaceAllAction = ReplaceAllAction.INSTANCE;
J
Johannes Rieken 已提交
385
		this.replaceAllAction.searchWidget = this;
S
Sandeep Somavarapu 已提交
386 387 388
		this.replaceAllAction.label = SearchWidget.REPLACE_ALL_DISABLED_LABEL;
		this.replaceActionBar = this._register(new ActionBar(this.replaceContainer));
		this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false });
S
Sandeep Somavarapu 已提交
389
		this.onkeydown(this.replaceActionBar.domNode, (keyboardEvent) => this.onReplaceActionbarKeyDown(keyboardEvent));
390

J
jeanp413 已提交
391
		this.replaceInputFocusTracker = this._register(dom.trackFocus(this.replaceInput.inputBox.inputElement));
392 393
		this._register(this.replaceInputFocusTracker.onDidFocus(() => this.replaceInputBoxFocused.set(true)));
		this._register(this.replaceInputFocusTracker.onDidBlur(() => this.replaceInputBoxFocused.set(false)));
394
		this._register(this.replaceInput.onPreserveCaseKeyDown((keyboardEvent: IKeyboardEvent) => this.onPreserveCaseKeyDown(keyboardEvent)));
395 396
	}

397
	triggerReplaceAll(): Promise<any> {
398
		this._onReplaceAll.fire();
399
		return Promise.resolve(null);
400 401
	}

J
Johannes Rieken 已提交
402
	private onToggleReplaceButton(): void {
S
Sandeep Somavarapu 已提交
403
		dom.toggleClass(this.replaceContainer, 'disabled');
404 405
		dom.toggleClass(this.toggleReplaceButton.element, 'codicon-chevron-right');
		dom.toggleClass(this.toggleReplaceButton.element, 'codicon-chevron-down');
406
		this.toggleReplaceButton.element.setAttribute('aria-expanded', this.isReplaceShown() ? 'true' : 'false');
407
		this.updateReplaceActiveState();
408
		this._onReplaceToggled.fire();
409 410
	}

411 412 413 414 415
	setValue(value: string, skipSearchOnChange: boolean) {
		this.searchInput.setValue(value);
		this.temporarilySkipSearchOnChange = skipSearchOnChange || this.temporarilySkipSearchOnChange;
	}

R
Rob Lourens 已提交
416
	setReplaceAllActionState(enabled: boolean): void {
417
		if (this.replaceAllAction.enabled !== enabled) {
J
Johannes Rieken 已提交
418
			this.replaceAllAction.enabled = enabled;
S
Sandeep Somavarapu 已提交
419
			this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keyBindingService) : SearchWidget.REPLACE_ALL_DISABLED_LABEL;
420 421 422 423 424
			this.updateReplaceActiveState();
		}
	}

	private updateReplaceActiveState(): void {
425 426
		const currentState = this.isReplaceActive();
		const newState = this.isReplaceShown() && this.replaceAllAction.enabled;
427
		if (currentState !== newState) {
S
Sandeep Somavarapu 已提交
428 429
			this.replaceActive.set(newState);
			this._onReplaceStateChange.fire(newState);
J
jeanp413 已提交
430
			this.replaceInput.inputBox.layout();
431
		}
432 433
	}

M
Matt Bierner 已提交
434
	private validateSearchInput(value: string): IMessage | null {
435 436 437 438 439 440 441
		if (value.length === 0) {
			return null;
		}
		if (!this.searchInput.getRegex()) {
			return null;
		}
		try {
442
			new RegExp(value, 'u');
443 444 445
		} catch (e) {
			return { content: e.message };
		}
446 447

		return null;
448 449
	}

S
Sandeep Somavarapu 已提交
450
	private onSearchInputChanged(): void {
451
		this.searchInput.clearMessage();
S
Sandeep Somavarapu 已提交
452
		this.setReplaceAllActionState(false);
453 454

		if (this.searchConfiguration.searchOnType) {
455 456 457 458
			if (this.temporarilySkipSearchOnChange) {
				this.temporarilySkipSearchOnChange = false;
			} else {
				this._onSearchCancel.fire({ focus: false });
459
				this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod);
460
			}
461
		}
S
Sandeep Somavarapu 已提交
462 463
	}

S
Sandeep Somavarapu 已提交
464
	private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) {
465 466 467 468 469
		if (keyboardEvent.equals(ctrlKeyMod | KeyCode.Enter)) {
			this.searchInput.inputBox.insertAtCursor('\n');
			keyboardEvent.preventDefault();
		}

S
Sandeep Somavarapu 已提交
470
		if (keyboardEvent.equals(KeyCode.Enter)) {
471
			this.searchInput.onSearchSubmit();
S
Sandeep Somavarapu 已提交
472 473 474 475 476
			this.submitSearch();
			keyboardEvent.preventDefault();
		}

		else if (keyboardEvent.equals(KeyCode.Escape)) {
477
			this._onSearchCancel.fire({ focus: true });
S
Sandeep Somavarapu 已提交
478 479 480 481 482 483 484 485 486 487 488
			keyboardEvent.preventDefault();
		}

		else if (keyboardEvent.equals(KeyCode.Tab)) {
			if (this.isReplaceShown()) {
				this.replaceInput.focus();
			} else {
				this.searchInput.focusOnCaseSensitive();
			}
			keyboardEvent.preventDefault();
		}
489 490

		else if (keyboardEvent.equals(KeyCode.UpArrow)) {
J
jeanp413 已提交
491
			stopPropagationForMultiLineUpwards(keyboardEvent, this.searchInput.getValue(), this.searchInput.domNode.querySelector('textarea'));
492 493 494
		}

		else if (keyboardEvent.equals(KeyCode.DownArrow)) {
J
jeanp413 已提交
495
			stopPropagationForMultiLineDownwards(keyboardEvent, this.searchInput.getValue(), this.searchInput.domNode.querySelector('textarea'));
496
		}
S
Sandeep Somavarapu 已提交
497 498 499 500 501 502 503 504 505 506 507 508
	}

	private onCaseSensitiveKeyDown(keyboardEvent: IKeyboardEvent) {
		if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
			if (this.isReplaceShown()) {
				this.replaceInput.focus();
				keyboardEvent.preventDefault();
			}
		}
	}

	private onRegexKeyDown(keyboardEvent: IKeyboardEvent) {
509 510 511 512 513 514 515 516 517
		if (keyboardEvent.equals(KeyCode.Tab)) {
			if (this.isReplaceShown()) {
				this.replaceInput.focusOnPreserve();
				keyboardEvent.preventDefault();
			}
		}
	}

	private onPreserveCaseKeyDown(keyboardEvent: IKeyboardEvent) {
S
Sandeep Somavarapu 已提交
518 519 520 521 522 523 524
		if (keyboardEvent.equals(KeyCode.Tab)) {
			if (this.isReplaceActive()) {
				this.focusReplaceAllAction();
			} else {
				this._onBlur.fire();
			}
			keyboardEvent.preventDefault();
525
		}
526 527 528 529
		else if (KeyMod.Shift | KeyCode.Tab) {
			this.focusRegexAction();
			keyboardEvent.preventDefault();
		}
530 531
	}

S
Sandeep Somavarapu 已提交
532
	private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) {
533
		if (keyboardEvent.equals(ctrlKeyMod | KeyCode.Enter)) {
J
jeanp413 已提交
534
			this.replaceInput.inputBox.insertAtCursor('\n');
535 536 537
			keyboardEvent.preventDefault();
		}

S
Sandeep Somavarapu 已提交
538 539 540 541 542 543 544 545 546 547 548 549 550
		if (keyboardEvent.equals(KeyCode.Enter)) {
			this.submitSearch();
			keyboardEvent.preventDefault();
		}

		else if (keyboardEvent.equals(KeyCode.Tab)) {
			this.searchInput.focusOnCaseSensitive();
			keyboardEvent.preventDefault();
		}

		else if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
			this.searchInput.focus();
			keyboardEvent.preventDefault();
551
		}
552 553

		else if (keyboardEvent.equals(KeyCode.UpArrow)) {
J
jeanp413 已提交
554
			stopPropagationForMultiLineUpwards(keyboardEvent, this.replaceInput.getValue(), this.replaceInput.domNode.querySelector('textarea'));
555 556 557
		}

		else if (keyboardEvent.equals(KeyCode.DownArrow)) {
J
jeanp413 已提交
558
			stopPropagationForMultiLineDownwards(keyboardEvent, this.replaceInput.getValue(), this.replaceInput.domNode.querySelector('textarea'));
559
		}
560 561
	}

S
Sandeep Somavarapu 已提交
562 563 564 565 566 567 568
	private onReplaceActionbarKeyDown(keyboardEvent: IKeyboardEvent) {
		if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
			this.focusRegexAction();
			keyboardEvent.preventDefault();
		}
	}

569
	private submitSearch(triggeredOnType = false): void {
R
Rob Lourens 已提交
570 571 572 573 574
		this.searchInput.validate();
		if (!this.searchInput.inputBox.isInputValid()) {
			return;
		}

575
		const value = this.searchInput.getValue();
R
Rob Lourens 已提交
576
		const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard;
577 578
		if (value && useGlobalFindBuffer) {
			this.clipboardServce.writeFindText(value);
579
		}
580
		this._onSearchSubmit.fire(triggeredOnType);
581 582
	}

R
Rob Lourens 已提交
583
	dispose(): void {
584
		this.setReplaceAllActionState(false);
585 586
		super.dispose();
	}
R
Rob Lourens 已提交
587 588 589 590

	private get searchConfiguration(): ISearchConfigurationProperties {
		return this.configurationService.getValue<ISearchConfigurationProperties>('search');
	}
591 592 593
}

export function registerContributions() {
J
Johannes Rieken 已提交
594 595
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: ReplaceAllAction.ID,
596
		weight: KeybindingWeight.WorkbenchContrib,
I
isidor 已提交
597
		when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE),
598 599
		primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter,
		handler: accessor => {
I
isidor 已提交
600
			if (isSearchViewFocused(accessor.get(IViewletService), accessor.get(IPanelService))) {
601 602 603 604
				ReplaceAllAction.INSTANCE.run();
			}
		}
	});
A
Alex Dima 已提交
605
}