searchWidget.ts 22.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 * as strings from 'vs/base/common/strings';
19
import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel';
20
import * as nls from 'vs/nls';
21 22
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
23 24 25 26
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';
27
import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
J
jeanp413 已提交
28
import { attachFindReplaceInputBoxStyler } from 'vs/platform/theme/common/styler';
29
import { IThemeService } from 'vs/platform/theme/common/themeService';
J
jeanp413 已提交
30
import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget';
31 32
import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
33 34 35
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';
36
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
37
import { isMacintosh } from 'vs/base/common/platform';
38 39

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

50 51
class ReplaceAllAction extends Action {

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

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

62
	private _searchWidget: SearchWidget | null = null;
63 64

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

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

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

80 81
const ctrlKeyMod = (isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd);

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

function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {
	const isMultiline = !!value.match(/\n/);
	if (textarea && isMultiline && textarea.selectionEnd < textarea.value.length) {
		event.stopPropagation();
		return;
	}
}

98 99
export class SearchWidget extends Widget {

100 101
	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 => {
102
		const kb = keyBindingService2.lookupKeybinding(ReplaceAllAction.ID);
B
Benjamin Pasero 已提交
103
		return appendKeyBindingLabel(nls.localize('search.action.replaceAll.enabled.label', "Replace All"), kb, keyBindingService2);
E
Erich Gamma 已提交
104
	}
S
Sandeep Somavarapu 已提交
105

106
	domNode!: HTMLElement;
107

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

112
	private replaceContainer!: HTMLElement;
J
jeanp413 已提交
113 114 115
	replaceInput!: ReplaceInput;
	replaceInputFocusTracker!: dom.IFocusTracker;
	private replaceInputBoxFocused: IContextKey<boolean>;
116 117
	private toggleReplaceButton!: Button;
	private replaceAllAction!: ReplaceAllAction;
A
Alex Dima 已提交
118
	private replaceActive: IContextKey<boolean>;
119
	private replaceActionBar!: ActionBar;
S
Sandeep Somavarapu 已提交
120
	private _replaceHistoryDelayer: Delayer<void>;
121

122
	private ignoreGlobalFindBufferOnNextFocus = false;
123
	private previousGlobalFindBufferValue: string | null = null;
124

125
	private _onSearchSubmit = this._register(new Emitter<void>());
R
Rob Lourens 已提交
126
	readonly onSearchSubmit: Event<void> = this._onSearchSubmit.event;
127 128

	private _onSearchCancel = this._register(new Emitter<void>());
R
Rob Lourens 已提交
129
	readonly onSearchCancel: Event<void> = this._onSearchCancel.event;
130 131

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

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

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

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

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

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

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

152
	constructor(
153
		container: HTMLElement,
154
		options: ISearchWidgetOptions,
155 156 157 158 159
		@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,
160 161
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IAccessibilityService private readonly accessibilityService: IAccessibilityService
162
	) {
163
		super();
S
Sandeep Somavarapu 已提交
164 165 166
		this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService);
		this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService);
		this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService);
S
Sandeep Somavarapu 已提交
167
		this._replaceHistoryDelayer = new Delayer<void>(500);
168
		this.render(container, options);
169 170 171 172 173 174

		this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('editor.accessibilitySupport')) {
				this.updateAccessibilitySupport();
			}
		});
175
		this.accessibilityService.onDidChangeAccessibilitySupport(() => this.updateAccessibilitySupport());
176
		this.updateAccessibilitySupport();
177 178
	}

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

182 183 184 185 186 187 188 189 190 191 192 193 194
		if (focusReplace && this.isReplaceShown()) {
			this.replaceInput.focus();
			if (select) {
				this.replaceInput.select();
			}
		} else {
			this.searchInput.focus();
			if (select) {
				this.searchInput.select();
			}
		}
	}

R
Rob Lourens 已提交
195
	setWidth(width: number) {
196
		this.searchInput.inputBox.layout();
J
Johannes Rieken 已提交
197
		this.replaceInput.width = width - 28;
J
jeanp413 已提交
198
		this.replaceInput.inputBox.layout();
199 200
	}

R
Rob Lourens 已提交
201
	clear() {
202
		this.searchInput.clear();
J
jeanp413 已提交
203
		this.replaceInput.setValue('');
S
Sandeep Somavarapu 已提交
204
		this.setReplaceAllActionState(false);
205 206
	}

R
Rob Lourens 已提交
207
	isReplaceShown(): boolean {
S
Sandeep Somavarapu 已提交
208
		return !dom.hasClass(this.replaceContainer, 'disabled');
209 210
	}

S
Sandeep Somavarapu 已提交
211
	isReplaceActive(): boolean {
212
		return !!this.replaceActive.get();
S
Sandeep Somavarapu 已提交
213 214
	}

R
Rob Lourens 已提交
215
	getReplaceValue(): string {
J
jeanp413 已提交
216
		return this.replaceInput.getValue();
217 218
	}

R
Rob Lourens 已提交
219
	toggleReplace(show?: boolean): void {
R
Rob Lourens 已提交
220
		if (show === undefined || show !== this.isReplaceShown()) {
221
			this.onToggleReplaceButton();
S
Sandeep Somavarapu 已提交
222 223 224
		}
	}

R
Rob Lourens 已提交
225
	getSearchHistory(): string[] {
226
		return this.searchInput.inputBox.getHistory();
227 228
	}

R
Rob Lourens 已提交
229
	getReplaceHistory(): string[] {
J
jeanp413 已提交
230
		return this.replaceInput.inputBox.getHistory();
231 232
	}

R
Rob Lourens 已提交
233
	clearHistory(): void {
234
		this.searchInput.inputBox.clearHistory();
235 236
	}

R
Rob Lourens 已提交
237
	showNextSearchTerm() {
238
		this.searchInput.inputBox.showNextValue();
239 240
	}

R
Rob Lourens 已提交
241
	showPreviousSearchTerm() {
242
		this.searchInput.inputBox.showPreviousValue();
243 244
	}

R
Rob Lourens 已提交
245
	showNextReplaceTerm() {
J
jeanp413 已提交
246
		this.replaceInput.inputBox.showNextValue();
247 248
	}

R
Rob Lourens 已提交
249
	showPreviousReplaceTerm() {
J
jeanp413 已提交
250
		this.replaceInput.inputBox.showPreviousValue();
251 252
	}

R
Rob Lourens 已提交
253
	searchInputHasFocus(): boolean {
M
Matt Bierner 已提交
254
		return !!this.searchInputBoxFocused.get();
255 256
	}

R
Rob Lourens 已提交
257
	replaceInputHasFocus(): boolean {
J
jeanp413 已提交
258
		return this.replaceInput.inputBox.hasFocus();
259 260
	}

R
Rob Lourens 已提交
261
	focusReplaceAllAction(): void {
S
Sandeep Somavarapu 已提交
262 263 264
		this.replaceActionBar.focus(true);
	}

R
Rob Lourens 已提交
265
	focusRegexAction(): void {
S
Sandeep Somavarapu 已提交
266 267 268
		this.searchInput.focusOnRegex();
	}

269 270 271 272
	private render(container: HTMLElement, options: ISearchWidgetOptions): void {
		this.domNode = dom.append(container, dom.$('.search-widget'));
		this.domNode.style.position = 'relative';

273 274 275
		this.renderToggleReplaceButton(this.domNode);

		this.renderSearchInput(this.domNode, options);
276
		this.renderReplaceInput(this.domNode, options);
277 278
	}

279
	private isScreenReaderOptimized() {
280
		const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled;
281 282 283 284
		const config = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
		return config === 'on' || (config === 'auto' && detected);
	}

285
	private updateAccessibilitySupport(): void {
286
		this.searchInput.setFocusInputOnOptionClick(!this.isScreenReaderOptimized());
287 288
	}

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

	private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
307
		const inputOptions: IFindInputOptions = {
308
			label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search or Escape to cancel'),
309
			validation: (value: string) => this.validateSearchInput(value),
S
Sandeep Somavarapu 已提交
310
			placeholder: nls.localize('search.placeHolder', "Search"),
S
Sandeep Somavarapu 已提交
311 312 313
			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),
314 315
			history: options.searchHistory,
			flexibleHeight: true
316 317
		};

318
		const searchInputContainer = dom.append(parent, dom.$('.search-container.input-box'));
319
		this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService, true));
J
jeanp413 已提交
320
		this._register(attachFindReplaceInputBoxStyler(this.searchInput, this.themeService));
S
Sandeep Somavarapu 已提交
321
		this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent));
322 323 324 325
		this.searchInput.setValue(options.value || '');
		this.searchInput.setRegex(!!options.isRegex);
		this.searchInput.setCaseSensitive(!!options.isCaseSensitive);
		this.searchInput.setWholeWords(!!options.isWholeWords);
326
		this._register(this.onSearchSubmit(() => {
S
Sandeep Somavarapu 已提交
327
			this.searchInput.inputBox.addToHistory();
328
		}));
329 330 331 332
		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()));
333

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

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

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

				this.previousGlobalFindBufferValue = globalBufferText;
			}

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

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

J
jeanp413 已提交
363 364
		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'),
365
			placeholder: nls.localize('search.replace.placeHolder', "Replace"),
J
jeanp413 已提交
366
			history: options.replaceHistory,
367
			flexibleHeight: true
J
jeanp413 已提交
368
		}, this.contextKeyService, true));
369

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

J
jeanp413 已提交
376 377 378 379 380
		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 已提交
381 382

		this.replaceAllAction = ReplaceAllAction.INSTANCE;
J
Johannes Rieken 已提交
383
		this.replaceAllAction.searchWidget = this;
S
Sandeep Somavarapu 已提交
384 385 386
		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 已提交
387
		this.onkeydown(this.replaceActionBar.domNode, (keyboardEvent) => this.onReplaceActionbarKeyDown(keyboardEvent));
388

J
jeanp413 已提交
389
		this.replaceInputFocusTracker = this._register(dom.trackFocus(this.replaceInput.inputBox.inputElement));
390 391
		this._register(this.replaceInputFocusTracker.onDidFocus(() => this.replaceInputBoxFocused.set(true)));
		this._register(this.replaceInputFocusTracker.onDidBlur(() => this.replaceInputBoxFocused.set(false)));
392 393
	}

394
	triggerReplaceAll(): Promise<any> {
395
		this._onReplaceAll.fire();
396
		return Promise.resolve(null);
397 398
	}

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

R
Rob Lourens 已提交
408
	setReplaceAllActionState(enabled: boolean): void {
409
		if (this.replaceAllAction.enabled !== enabled) {
J
Johannes Rieken 已提交
410
			this.replaceAllAction.enabled = enabled;
S
Sandeep Somavarapu 已提交
411
			this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keyBindingService) : SearchWidget.REPLACE_ALL_DISABLED_LABEL;
412 413 414 415 416
			this.updateReplaceActiveState();
		}
	}

	private updateReplaceActiveState(): void {
417 418
		const currentState = this.isReplaceActive();
		const newState = this.isReplaceShown() && this.replaceAllAction.enabled;
419
		if (currentState !== newState) {
S
Sandeep Somavarapu 已提交
420 421
			this.replaceActive.set(newState);
			this._onReplaceStateChange.fire(newState);
J
jeanp413 已提交
422
			this.replaceInput.inputBox.layout();
423
		}
424 425
	}

M
Matt Bierner 已提交
426
	private validateSearchInput(value: string): IMessage | null {
427 428 429 430 431 432 433
		if (value.length === 0) {
			return null;
		}
		if (!this.searchInput.getRegex()) {
			return null;
		}
		try {
434
			// tslint:disable-next-line: no-unused-expression
435
			new RegExp(value, 'u');
436 437 438
		} catch (e) {
			return { content: e.message };
		}
439 440

		if (strings.regExpContainsBackreference(value)) {
R
Rob Lourens 已提交
441 442 443
			if (!this.searchConfiguration.usePCRE2) {
				return { content: nls.localize('regexp.backreferenceValidationFailure', "Backreferences are not supported") };
			}
444 445 446
		}

		return null;
447 448
	}

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

S
Sandeep Somavarapu 已提交
454
	private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) {
455 456 457 458 459
		if (keyboardEvent.equals(ctrlKeyMod | KeyCode.Enter)) {
			this.searchInput.inputBox.insertAtCursor('\n');
			keyboardEvent.preventDefault();
		}

S
Sandeep Somavarapu 已提交
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
		if (keyboardEvent.equals(KeyCode.Enter)) {
			this.submitSearch();
			keyboardEvent.preventDefault();
		}

		else if (keyboardEvent.equals(KeyCode.Escape)) {
			this._onSearchCancel.fire();
			keyboardEvent.preventDefault();
		}

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

		else if (keyboardEvent.equals(KeyCode.UpArrow)) {
J
jeanp413 已提交
480
			stopPropagationForMultiLineUpwards(keyboardEvent, this.searchInput.getValue(), this.searchInput.domNode.querySelector('textarea'));
481 482 483
		}

		else if (keyboardEvent.equals(KeyCode.DownArrow)) {
J
jeanp413 已提交
484
			stopPropagationForMultiLineDownwards(keyboardEvent, this.searchInput.getValue(), this.searchInput.domNode.querySelector('textarea'));
485
		}
S
Sandeep Somavarapu 已提交
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
	}

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

	private onRegexKeyDown(keyboardEvent: IKeyboardEvent) {
		if (keyboardEvent.equals(KeyCode.Tab)) {
			if (this.isReplaceActive()) {
				this.focusReplaceAllAction();
			} else {
				this._onBlur.fire();
			}
			keyboardEvent.preventDefault();
505 506 507
		}
	}

S
Sandeep Somavarapu 已提交
508
	private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) {
509
		if (keyboardEvent.equals(ctrlKeyMod | KeyCode.Enter)) {
J
jeanp413 已提交
510
			this.replaceInput.inputBox.insertAtCursor('\n');
511 512 513
			keyboardEvent.preventDefault();
		}

S
Sandeep Somavarapu 已提交
514 515 516 517 518 519 520 521 522 523 524 525 526
		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();
527
		}
528 529

		else if (keyboardEvent.equals(KeyCode.UpArrow)) {
J
jeanp413 已提交
530
			stopPropagationForMultiLineUpwards(keyboardEvent, this.replaceInput.getValue(), this.replaceInput.domNode.querySelector('textarea'));
531 532 533
		}

		else if (keyboardEvent.equals(KeyCode.DownArrow)) {
J
jeanp413 已提交
534
			stopPropagationForMultiLineDownwards(keyboardEvent, this.replaceInput.getValue(), this.replaceInput.domNode.querySelector('textarea'));
535
		}
536 537
	}

S
Sandeep Somavarapu 已提交
538 539 540 541 542 543 544
	private onReplaceActionbarKeyDown(keyboardEvent: IKeyboardEvent) {
		if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
			this.focusRegexAction();
			keyboardEvent.preventDefault();
		}
	}

545
	private submitSearch(): void {
R
Rob Lourens 已提交
546 547 548 549 550
		this.searchInput.validate();
		if (!this.searchInput.inputBox.isInputValid()) {
			return;
		}

551
		const value = this.searchInput.getValue();
R
Rob Lourens 已提交
552
		const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard;
553
		if (value) {
554 555 556 557
			if (useGlobalFindBuffer) {
				this.clipboardServce.writeFindText(value);
			}

558
			this._onSearchSubmit.fire();
559 560 561
		}
	}

R
Rob Lourens 已提交
562
	dispose(): void {
563
		this.setReplaceAllActionState(false);
564 565
		super.dispose();
	}
R
Rob Lourens 已提交
566 567 568 569

	private get searchConfiguration(): ISearchConfigurationProperties {
		return this.configurationService.getValue<ISearchConfigurationProperties>('search');
	}
570 571 572
}

export function registerContributions() {
J
Johannes Rieken 已提交
573 574
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: ReplaceAllAction.ID,
575
		weight: KeybindingWeight.WorkbenchContrib,
I
isidor 已提交
576
		when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE),
577 578
		primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter,
		handler: accessor => {
I
isidor 已提交
579
			if (isSearchViewFocused(accessor.get(IViewletService), accessor.get(IPanelService))) {
580 581 582 583
				ReplaceAllAction.INSTANCE.run();
			}
		}
	});
A
Alex Dima 已提交
584
}