searchView.ts 61.5 KB
Newer Older
E
Erich Gamma 已提交
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 browser from 'vs/base/browser/browser';
7
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
8
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
9
import * as aria from 'vs/base/browser/ui/aria/aria';
10
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
11 12 13
import { IAction } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
14
import { anyEvent, debounceEvent, Emitter } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
15
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
16
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
17 18 19
import * as paths from 'vs/base/common/paths';
import * as env from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
20
import { URI } from 'vs/base/common/uri';
S
Sandeep Somavarapu 已提交
21
import { ITree } from 'vs/base/parts/tree/browser/tree';
22
import 'vs/css!./media/searchview';
23 24 25
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
26
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
27
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
J
Johannes Rieken 已提交
28
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
29 30
import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
31
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
32
import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService';
33
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
J
Johannes Rieken 已提交
34
import { IProgressService } from 'vs/platform/progress/common/progress';
J
Joao Moreno 已提交
35
import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchHistoryService, ISearchHistoryValues, ISearchProgressItem, ITextQuery, VIEW_ID, SearchErrorCode } from 'vs/platform/search/common/search';
B
Benjamin Pasero 已提交
36
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
J
Johannes Rieken 已提交
37
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
38
import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
39 40 41
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
42
import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd';
43
import { Viewlet } from 'vs/workbench/browser/viewlet';
44
import { IEditor } from 'vs/workbench/common/editor';
45 46
import { IPanel } from 'vs/workbench/common/panel';
import { IViewlet } from 'vs/workbench/common/viewlet';
47 48 49 50 51
import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/parts/search/browser/patternInputWidget';
import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction } from 'vs/workbench/parts/search/browser/searchActions';
import { SearchAccessibilityProvider, SearchDataSource, SearchFilter, SearchRenderer, SearchSorter, SearchTreeController } from 'vs/workbench/parts/search/browser/searchResultsView';
import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget';
import * as Constants from 'vs/workbench/parts/search/common/constants';
52
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder';
53 54 55 56
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search';
import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, SearchModel } from 'vs/workbench/parts/search/common/searchModel';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
R
Rob Lourens 已提交
57
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
S
Sandeep Somavarapu 已提交
58
import { IPartService } from 'vs/workbench/services/part/common/partService';
59
import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
60
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
E
Erich Gamma 已提交
61

62 63
const $ = dom.$;

64
export class SearchView extends Viewlet implements IViewlet, IPanel {
E
Erich Gamma 已提交
65

R
Rob Lourens 已提交
66
	private static readonly MAX_TEXT_RESULTS = 10000;
S
Sandeep Somavarapu 已提交
67

68
	private static readonly WIDE_CLASS_NAME = 'wide';
R
Rob Lourens 已提交
69
	private static readonly WIDE_VIEW_SIZE = 1000;
70

E
Erich Gamma 已提交
71
	private isDisposed: boolean;
72

E
Erich Gamma 已提交
73
	private queryBuilder: QueryBuilder;
74
	private viewModel: SearchModel;
E
Erich Gamma 已提交
75

A
Alex Dima 已提交
76
	private viewletVisible: IContextKey<boolean>;
S
Sandeep Somavarapu 已提交
77
	private viewletFocused: IContextKey<boolean>;
78 79
	private inputBoxFocused: IContextKey<boolean>;
	private inputPatternIncludesFocused: IContextKey<boolean>;
80
	private inputPatternExclusionsFocused: IContextKey<boolean>;
81 82
	private firstMatchFocused: IContextKey<boolean>;
	private fileMatchOrMatchFocused: IContextKey<boolean>;
83
	private fileMatchOrFolderMatchFocus: IContextKey<boolean>;
84
	private fileMatchFocused: IContextKey<boolean>;
85
	private folderMatchFocused: IContextKey<boolean>;
86
	private matchFocused: IContextKey<boolean>;
87
	private hasSearchResultsKey: IContextKey<boolean>;
R
Rob Lourens 已提交
88

S
Sandeep Somavarapu 已提交
89
	private searchSubmitted: boolean;
90
	private searching: boolean;
S
Sandeep Somavarapu 已提交
91

92
	private actions: (RefreshAction | CollapseDeepestExpandedLevelAction | ClearSearchResultsAction | CancelSearchAction)[] = [];
93
	private tree: WorkbenchTree;
B
Benjamin Pasero 已提交
94
	private viewletState: object;
95 96
	private messagesElement: HTMLElement;
	private messageDisposables: IDisposable[] = [];
97
	private searchWidgetsContainerElement: HTMLElement;
98
	private searchWidget: SearchWidget;
99
	private size: dom.Dimension;
E
Erich Gamma 已提交
100
	private queryDetails: HTMLElement;
S
Sandeep Somavarapu 已提交
101
	private toggleQueryDetailsButton: HTMLElement;
102 103
	private inputPatternExcludes: ExcludePatternInputWidget;
	private inputPatternIncludes: PatternInputWidget;
104
	private resultsElement: HTMLElement;
E
Erich Gamma 已提交
105

S
Sandeep Somavarapu 已提交
106 107
	private currentSelectedFileMatch: FileMatch;

M
Matt Bierner 已提交
108
	private readonly selectCurrentMatchEmitter: Emitter<string>;
S
Sandeep Somavarapu 已提交
109
	private delayedRefresh: Delayer<void>;
110
	private changedWhileHidden: boolean;
R
Rob Lourens 已提交
111

112
	private searchWithoutFolderMessageElement: HTMLElement;
113

114
	constructor(
S
Sandeep Somavarapu 已提交
115
		@IPartService partService: IPartService,
116
		@ITelemetryService telemetryService: ITelemetryService,
117
		@IFileService private fileService: IFileService,
118
		@IEditorService private editorService: IEditorService,
119
		@IProgressService private progressService: IProgressService,
120
		@INotificationService private notificationService: INotificationService,
121
		@IDialogService private dialogService: IDialogService,
B
Benjamin Pasero 已提交
122
		@IStorageService storageService: IStorageService,
123 124
		@IContextViewService private contextViewService: IContextViewService,
		@IInstantiationService private instantiationService: IInstantiationService,
125
		@IConfigurationService configurationService: IConfigurationService,
126
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
S
Sandeep Somavarapu 已提交
127
		@ISearchWorkbenchService private searchWorkbenchService: ISearchWorkbenchService,
128
		@IContextKeyService private contextKeyService: IContextKeyService,
129
		@IReplaceService private replaceService: IReplaceService,
130
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
131
		@IPreferencesService private preferencesService: IPreferencesService,
132
		@IThemeService protected themeService: IThemeService,
R
Rob Lourens 已提交
133 134
		@ISearchHistoryService private searchHistoryService: ISearchHistoryService,
		@IEditorGroupsService private editorGroupsService: IEditorGroupsService
E
Erich Gamma 已提交
135
	) {
136
		super(VIEW_ID, configurationService, partService, telemetryService, themeService, storageService);
E
Erich Gamma 已提交
137

I
isidor 已提交
138
		this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService);
S
Sandeep Somavarapu 已提交
139
		this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService);
140 141
		this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
		this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService);
142
		this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService);
143 144
		this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService);
		this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService);
145
		this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService);
146
		this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService);
147
		this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService);
148
		this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
149
		this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
E
Erich Gamma 已提交
150 151

		this.queryBuilder = this.instantiationService.createInstance(QueryBuilder);
B
Benjamin Pasero 已提交
152
		this.viewletState = this.getMemento(StorageScope.WORKSPACE);
E
Erich Gamma 已提交
153

B
Benjamin Pasero 已提交
154 155 156
		this._register(this.fileService.onFileChanges(e => this.onFilesChanged(e)));
		this._register(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e)));
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState()));
157
		this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
158

159 160 161
		this.selectCurrentMatchEmitter = this._register(new Emitter<string>());
		this._register(debounceEvent(this.selectCurrentMatchEmitter.event, (l, e) => e, 100, /*leading=*/true)
			(() => this.selectCurrentMatch()));
R
Rob Lourens 已提交
162

163
		this.delayedRefresh = this._register(new Delayer<void>(250));
E
Erich Gamma 已提交
164 165
	}

166
	private onDidChangeWorkbenchState(): void {
167 168
		if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.searchWithoutFolderMessageElement) {
			dom.hide(this.searchWithoutFolderMessageElement);
169 170 171
		}
	}

I
isidor 已提交
172
	public create(parent: HTMLElement): void {
E
Erich Gamma 已提交
173 174
		super.create(parent);

B
Benjamin Pasero 已提交
175
		this.viewModel = this._register(this.searchWorkbenchService.searchModel);
176
		dom.addClass(parent, 'search-view');
E
Erich Gamma 已提交
177

178
		this.searchWidgetsContainerElement = dom.append(parent, $('.search-widgets-container'));
179
		this.createSearchWidget(this.searchWidgetsContainerElement);
180

181
		const history = this.searchHistoryService.load();
B
Benjamin Pasero 已提交
182 183
		const filePatterns = this.viewletState['query.filePatterns'] || '';
		const patternExclusions = this.viewletState['query.folderExclusions'] || '';
R
Rob Lourens 已提交
184
		const patternExclusionsHistory: string[] = history.exclude || [];
B
Benjamin Pasero 已提交
185
		const patternIncludes = this.viewletState['query.folderIncludes'] || '';
R
Rob Lourens 已提交
186
		const patternIncludesHistory: string[] = history.include || [];
B
Benjamin Pasero 已提交
187 188 189
		const queryDetailsExpanded = this.viewletState['query.queryDetailsExpanded'] || '';
		const useExcludesAndIgnoreFiles = typeof this.viewletState['query.useExcludesAndIgnoreFiles'] === 'boolean' ?
			this.viewletState['query.useExcludesAndIgnoreFiles'] : true;
190

191
		this.queryDetails = dom.append(this.searchWidgetsContainerElement, $('.query-details'));
S
Sandeep Somavarapu 已提交
192

193 194 195
		// Toggle query details button
		this.toggleQueryDetailsButton = dom.append(this.queryDetails,
			$('.more', { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") }));
E
Erich Gamma 已提交
196

197 198
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => {
			dom.EventHelper.stop(e);
199
			this.toggleQueryDetails(!this.isScreenReaderOptimized());
200 201 202
		}));
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
			const event = new StandardKeyboardEvent(e);
E
Erich Gamma 已提交
203

204 205 206 207 208 209 210
			if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
				dom.EventHelper.stop(e);
				this.toggleQueryDetails(false);
			}
		}));
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
			const event = new StandardKeyboardEvent(e);
E
Erich Gamma 已提交
211

212 213 214 215 216 217 218 219 220
			if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
				if (this.searchWidget.isReplaceActive()) {
					this.searchWidget.focusReplaceAllAction();
				} else {
					this.searchWidget.focusRegexAction();
				}
				dom.EventHelper.stop(e);
			}
		}));
E
Erich Gamma 已提交
221

222 223 224 225 226
		// folder includes list
		const folderIncludesList = dom.append(this.queryDetails,
			$('.file-types.includes'));
		const filesToIncludeTitle = nls.localize('searchScope.includes', "files to include");
		dom.append(folderIncludesList, $('h4', undefined, filesToIncludeTitle));
227

228 229 230 231
		this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, {
			ariaLabel: nls.localize('label.includes', 'Search Include Patterns'),
			history: patternIncludesHistory,
		}));
232

233
		this.inputPatternIncludes.setValue(patternIncludes);
234

235
		this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true));
236 237 238 239 240 241 242 243 244 245 246
		this.inputPatternIncludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget
		this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused);

		// excludes list
		const excludesList = dom.append(this.queryDetails, $('.file-types.excludes'));
		const excludesTitle = nls.localize('searchScope.excludes', "files to exclude");
		dom.append(excludesList, $('h4', undefined, excludesTitle));
		this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, {
			ariaLabel: nls.localize('label.excludes', 'Search Exclude Patterns'),
			history: patternExclusionsHistory,
		}));
247

248 249
		this.inputPatternExcludes.setValue(patternExclusions);
		this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles);
250

251
		this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true));
252 253 254
		this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget
		this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused);

255
		this.messagesElement = dom.append(parent, $('.messages'));
256
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
257
			this.showSearchWithoutFolderMessage();
258
		}
259

260
		this.createSearchResultsView(parent);
261

S
Sandeep Somavarapu 已提交
262 263 264 265 266
		this.actions = [
			this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL),
			this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL),
			this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)
		];
267

268
		if (filePatterns !== '' || patternExclusions !== '' || patternIncludes !== '' || queryDetailsExpanded !== '' || !useExcludesAndIgnoreFiles) {
269
			this.toggleQueryDetails(true, true, true);
270 271
		}

B
Benjamin Pasero 已提交
272
		this._register(this.viewModel.searchResult.onChange((event) => this.onSearchResultsChanged(event)));
S
Sandeep Somavarapu 已提交
273

S
Sandeep Somavarapu 已提交
274 275
		this._register(this.onDidFocus(() => this.viewletFocused.set(true)));
		this._register(this.onDidBlur(() => this.viewletFocused.set(false)));
276 277
	}

J
Johannes Rieken 已提交
278
	public get searchAndReplaceWidget(): SearchWidget {
279 280 281
		return this.searchWidget;
	}

282 283 284 285
	public get searchIncludePattern(): PatternInputWidget {
		return this.inputPatternIncludes;
	}

286 287 288 289
	public get searchExcludePattern(): PatternInputWidget {
		return this.inputPatternExcludes;
	}

S
Sandeep Somavarapu 已提交
290 291 292 293 294 295
	private updateActions(): void {
		for (const action of this.actions) {
			action.update();
		}
	}

296 297 298 299 300 301
	private isScreenReaderOptimized() {
		const detected = browser.getAccessibilitySupport() === env.AccessibilitySupport.Enabled;
		const config = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
		return config === 'on' || (config === 'auto' && detected);
	}

302
	private createSearchWidget(container: HTMLElement): void {
B
Benjamin Pasero 已提交
303 304 305 306
		let contentPattern = this.viewletState['query.contentPattern'] || '';
		let isRegex = this.viewletState['query.regex'] === true;
		let isWholeWords = this.viewletState['query.wholeWords'] === true;
		let isCaseSensitive = this.viewletState['query.caseSensitive'] === true;
307
		const history = this.searchHistoryService.load();
B
Benjamin Pasero 已提交
308 309
		let searchHistory = history.search || this.viewletState['query.searchHistory'] || [];
		let replaceHistory = history.replace || this.viewletState['query.replaceHistory'] || [];
B
Benjamin Pasero 已提交
310
		let showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true;
E
Erich Gamma 已提交
311

312
		this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, <ISearchWidgetOptions>{
313 314 315
			value: contentPattern,
			isRegex: isRegex,
			isCaseSensitive: isCaseSensitive,
316
			isWholeWords: isWholeWords,
317
			searchHistory: searchHistory,
318
			replaceHistory: replaceHistory
B
Benjamin Pasero 已提交
319
		}));
S
Sandeep Somavarapu 已提交
320

B
Benjamin Pasero 已提交
321
		if (showReplace) {
S
Sandeep Somavarapu 已提交
322 323 324
			this.searchWidget.toggleReplace(true);
		}

325
		this._register(this.searchWidget.onSearchSubmit(() => this.onQueryChanged()));
B
Benjamin Pasero 已提交
326
		this._register(this.searchWidget.onSearchCancel(() => this.cancelSearch()));
327
		this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true)));
328

B
Benjamin Pasero 已提交
329 330
		this._register(this.searchWidget.onReplaceToggled(() => this.onReplaceToggled()));
		this._register(this.searchWidget.onReplaceStateChange((state) => {
J
Johannes Rieken 已提交
331
			this.viewModel.replaceActive = state;
332
			this.tree.refresh();
S
Sandeep Somavarapu 已提交
333
		}));
B
Benjamin Pasero 已提交
334
		this._register(this.searchWidget.onReplaceValueChanged((value) => {
J
Johannes Rieken 已提交
335
			this.viewModel.replaceString = this.searchWidget.getReplaceValue();
S
Sandeep Somavarapu 已提交
336
			this.delayedRefresh.trigger(() => this.tree.refresh());
S
Sandeep Somavarapu 已提交
337
		}));
338

B
Benjamin Pasero 已提交
339
		this._register(this.searchWidget.onBlur(() => {
S
Sandeep Somavarapu 已提交
340 341 342
			this.toggleQueryDetailsButton.focus();
		}));

B
Benjamin Pasero 已提交
343
		this._register(this.searchWidget.onReplaceAll(() => this.replaceAll()));
344 345 346 347
		this.trackInputBox(this.searchWidget.searchInputFocusTracker);
		this.trackInputBox(this.searchWidget.replaceInputFocusTracker);
	}

348
	private trackInputBox(inputFocusTracker: dom.IFocusTracker, contextKey?: IContextKey<boolean>): void {
B
Benjamin Pasero 已提交
349
		this._register(inputFocusTracker.onDidFocus(() => {
350
			this.inputBoxFocused.set(true);
351 352 353
			if (contextKey) {
				contextKey.set(true);
			}
354
		}));
B
Benjamin Pasero 已提交
355
		this._register(inputFocusTracker.onDidBlur(() => {
356
			this.inputBoxFocused.set(this.searchWidget.searchInputHasFocus()
357
				|| this.searchWidget.replaceInputHasFocus()
358 359
				|| this.inputPatternIncludes.inputHasFocus()
				|| this.inputPatternExcludes.inputHasFocus());
360 361 362
			if (contextKey) {
				contextKey.set(false);
			}
363
		}));
S
Sandeep Somavarapu 已提交
364 365
	}

S
Sandeep Somavarapu 已提交
366 367 368 369
	private onReplaceToggled(): void {
		this.layout(this.size);
	}

370
	private onSearchResultsChanged(event?: IChangeEvent): Thenable<any> {
371 372 373 374
		if (this.isVisible()) {
			return this.refreshAndUpdateCount(event);
		} else {
			this.changedWhileHidden = true;
375
			return Promise.resolve(null);
376 377 378
		}
	}

379
	private refreshAndUpdateCount(event?: IChangeEvent): Thenable<void> {
R
Rob Lourens 已提交
380 381 382
		this.searchWidget.setReplaceAllActionState(!this.viewModel.searchResult.isEmpty());
		this.updateSearchResultCount(this.viewModel.searchResult.query.userDisabledExcludesAndIgnoreFiles);
		return this.refreshTree(event);
383 384
	}

385
	private refreshTree(event?: IChangeEvent): Thenable<any> {
386
		if (!event || event.added || event.removed) {
S
Sandeep Somavarapu 已提交
387
			return this.tree.refresh(this.viewModel.searchResult);
388 389
		} else {
			if (event.elements.length === 1) {
S
Sandeep Somavarapu 已提交
390
				return this.tree.refresh(event.elements[0]);
391
			} else {
S
Sandeep Somavarapu 已提交
392
				return this.tree.refresh(event.elements);
393 394 395 396
			}
		}
	}

397
	private replaceAll(): void {
398
		if (this.viewModel.searchResult.count() === 0) {
S
Sandeep Somavarapu 已提交
399 400 401
			return;
		}

J
Johannes Rieken 已提交
402
		let progressRunner = this.progressService.show(100);
403

J
Johannes Rieken 已提交
404 405 406
		let occurrences = this.viewModel.searchResult.count();
		let fileCount = this.viewModel.searchResult.fileCount();
		let replaceValue = this.searchWidget.getReplaceValue() || '';
407
		let afterReplaceAllMessage = this.buildAfterReplaceAllMessage(occurrences, fileCount, replaceValue);
408

B
Benjamin Pasero 已提交
409
		let confirmation: IConfirmation = {
410
			title: nls.localize('replaceAll.confirmation.title', "Replace All"),
411
			message: this.buildReplaceAllConfirmationMessage(occurrences, fileCount, replaceValue),
412
			primaryButton: nls.localize('replaceAll.confirm.button', "&&Replace"),
B
Benjamin Pasero 已提交
413
			type: 'question'
414 415
		};

416
		this.dialogService.confirm(confirmation).then(res => {
417
			if (res.confirmed) {
418 419 420
				this.searchWidget.setReplaceAllActionState(false);
				this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
					progressRunner.done();
421
					const messageEl = this.clearMessage();
422
					dom.append(messageEl, $('p', undefined, afterReplaceAllMessage));
423 424 425
				}, (error) => {
					progressRunner.done();
					errors.isPromiseCanceledError(error);
426
					this.notificationService.error(error);
427 428 429
				});
			}
		});
430 431
	}

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
	private buildAfterReplaceAllMessage(occurrences: number, fileCount: number, replaceValue?: string) {
		if (occurrences === 1) {
			if (fileCount === 1) {
				if (replaceValue) {
					return nls.localize('replaceAll.occurrence.file.message', "Replaced {0} occurrence across {1} file with '{2}'.", occurrences, fileCount, replaceValue);
				}

				return nls.localize('removeAll.occurrence.file.message', "Replaced {0} occurrence across {1} file'.", occurrences, fileCount);
			}

			if (replaceValue) {
				return nls.localize('replaceAll.occurrence.files.message', "Replaced {0} occurrence across {1} files with '{2}'.", occurrences, fileCount, replaceValue);
			}

			return nls.localize('removeAll.occurrence.files.message', "Replaced {0} occurrence across {1} files.", occurrences, fileCount);
		}

		if (fileCount === 1) {
			if (replaceValue) {
				return nls.localize('replaceAll.occurrences.file.message', "Replaced {0} occurrences across {1} file with '{2}'.", occurrences, fileCount, replaceValue);
			}

			return nls.localize('removeAll.occurrences.file.message', "Replaced {0} occurrences across {1} file'.", occurrences, fileCount);
		}

		if (replaceValue) {
			return nls.localize('replaceAll.occurrences.files.message', "Replaced {0} occurrences across {1} files with '{2}'.", occurrences, fileCount, replaceValue);
		}

		return nls.localize('removeAll.occurrences.files.message', "Replaced {0} occurrences across {1} files.", occurrences, fileCount);
	}

	private buildReplaceAllConfirmationMessage(occurrences: number, fileCount: number, replaceValue?: string) {
		if (occurrences === 1) {
			if (fileCount === 1) {
				if (replaceValue) {
					return nls.localize('removeAll.occurrence.file.confirmation.message', "Replace {0} occurrence across {1} file with '{2}'?", occurrences, fileCount, replaceValue);
				}

				return nls.localize('replaceAll.occurrence.file.confirmation.message', "Replace {0} occurrence across {1} file'?", occurrences, fileCount);
			}

			if (replaceValue) {
				return nls.localize('removeAll.occurrence.files.confirmation.message', "Replace {0} occurrence across {1} files with '{2}'?", occurrences, fileCount, replaceValue);
			}

			return nls.localize('replaceAll.occurrence.files.confirmation.message', "Replace {0} occurrence across {1} files?", occurrences, fileCount);
		}

		if (fileCount === 1) {
			if (replaceValue) {
				return nls.localize('removeAll.occurrences.file.confirmation.message', "Replace {0} occurrences across {1} file with '{2}'?", occurrences, fileCount, replaceValue);
			}

			return nls.localize('replaceAll.occurrences.file.confirmation.message', "Replace {0} occurrences across {1} file'?", occurrences, fileCount);
		}

		if (replaceValue) {
			return nls.localize('removeAll.occurrences.files.confirmation.message', "Replace {0} occurrences across {1} files with '{2}'?", occurrences, fileCount, replaceValue);
		}

		return nls.localize('replaceAll.occurrences.files.confirmation.message', "Replace {0} occurrences across {1} files?", occurrences, fileCount);
	}

496 497
	private clearMessage(): HTMLElement {
		this.searchWithoutFolderMessageElement = void 0;
498

499 500 501 502 503
		dom.clearNode(this.messagesElement);
		dom.show(this.messagesElement);
		dispose(this.messageDisposables);
		this.messageDisposables = [];

504
		return dom.append(this.messagesElement, $('.message'));
505 506
	}

507 508 509
	private createSearchResultsView(container: HTMLElement): void {
		this.resultsElement = dom.append(container, $('.results.show-file-icons'));
		const dataSource = this._register(this.instantiationService.createInstance(SearchDataSource));
510
		const renderer = this._register(this.instantiationService.createInstance(SearchRenderer, this));
511 512 513 514 515 516 517 518 519 520 521 522 523
		const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, (obj: any) => obj instanceof FileMatch ? obj.resource() : void 0);

		this.tree = this._register(this.instantiationService.createInstance(WorkbenchTree, this.resultsElement, {
			dataSource: dataSource,
			renderer: renderer,
			sorter: new SearchSorter(),
			filter: new SearchFilter(),
			controller: this.instantiationService.createInstance(SearchTreeController),
			accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider),
			dnd
		}, {
				ariaLabel: nls.localize('treeAriaLabel', "Search Results"),
				showLoading: false
S
Sandeep Somavarapu 已提交
524
			}));
525

526 527 528 529 530 531 532 533
		this.tree.setInput(this.viewModel.searchResult);

		const searchResultsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true }));
		this._register(debounceEvent(searchResultsNavigator.openResource, (last, event) => event, 75, true)(options => {
			if (options.element instanceof Match) {
				let selectedMatch: Match = options.element;
				if (this.currentSelectedFileMatch) {
					this.currentSelectedFileMatch.setSelectedMatch(null);
534
				}
535 536 537 538 539 540 541
				this.currentSelectedFileMatch = selectedMatch.parent();
				this.currentSelectedFileMatch.setSelectedMatch(selectedMatch);
				if (!(options.payload && options.payload.preventEditorOpen)) {
					this.onFocus(selectedMatch, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned);
				}
			}
		}));
S
Sandeep Somavarapu 已提交
542

543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
		this._register(anyEvent<any>(this.tree.onDidFocus, this.tree.onDidChangeFocus)(() => {
			if (this.tree.isDOMFocused()) {
				const focus = this.tree.getFocus();
				this.firstMatchFocused.set(this.tree.getNavigator().first() === focus);
				this.fileMatchOrMatchFocused.set(!!focus);
				this.fileMatchFocused.set(focus instanceof FileMatch);
				this.folderMatchFocused.set(focus instanceof FolderMatch);
				this.matchFocused.set(focus instanceof Match);
				this.fileMatchOrFolderMatchFocus.set(focus instanceof FileMatch || focus instanceof FolderMatch);
			}
		}));

		this._register(this.tree.onDidBlur(e => {
			this.firstMatchFocused.reset();
			this.fileMatchOrMatchFocused.reset();
			this.fileMatchFocused.reset();
			this.folderMatchFocused.reset();
			this.matchFocused.reset();
			this.fileMatchOrFolderMatchFocus.reset();
		}));
E
Erich Gamma 已提交
563 564
	}

R
Rob Lourens 已提交
565 566
	public selectCurrentMatch(): void {
		const focused = this.tree.getFocus();
567
		const eventPayload = { focusEditor: true };
R
Rob Lourens 已提交
568 569 570 571
		this.tree.setSelection([focused], eventPayload);
	}

	public selectNextMatch(): void {
572
		const [selected]: FileMatchOrMatch[] = this.tree.getSelection();
573

R
Rob Lourens 已提交
574 575 576 577 578 579 580 581 582 583
		// Expand the initial selected node, if needed
		if (selected instanceof FileMatch) {
			if (!this.tree.isExpanded(selected)) {
				this.tree.expand(selected);
			}
		}

		let navigator = this.tree.getNavigator(selected, /*subTreeOnly=*/false);

		let next = navigator.next();
584
		if (!next) {
R
Rob Lourens 已提交
585 586 587 588
			// Reached the end - get a new navigator from the root.
			// .first and .last only work when subTreeOnly = true. Maybe there's a simpler way.
			navigator = this.tree.getNavigator(this.tree.getInput(), /*subTreeOnly*/true);
			next = navigator.first();
589 590 591
		}

		// Expand and go past FileMatch nodes
R
Rob Lourens 已提交
592
		while (!(next instanceof Match)) {
593 594
			if (!this.tree.isExpanded(next)) {
				this.tree.expand(next);
595 596 597
			}

			// Select the FileMatch's first child
598
			next = navigator.next();
599 600 601
		}

		// Reveal the newly selected element
602 603 604 605 606 607 608
		if (next) {
			const eventPayload = { preventEditorOpen: true };
			this.tree.setFocus(next, eventPayload);
			this.tree.setSelection([next], eventPayload);
			this.tree.reveal(next);
			this.selectCurrentMatchEmitter.fire();
		}
609 610
	}

R
Rob Lourens 已提交
611
	public selectPreviousMatch(): void {
612
		const [selected]: FileMatchOrMatch[] = this.tree.getSelection();
R
Rob Lourens 已提交
613
		let navigator = this.tree.getNavigator(selected, /*subTreeOnly=*/false);
614

615
		let prev = navigator.previous();
616 617

		// Expand and go past FileMatch nodes
618 619
		if (!(prev instanceof Match)) {
			prev = navigator.previous();
R
Rob Lourens 已提交
620 621 622 623 624 625 626 627 628 629 630 631 632 633
			if (!prev) {
				// Wrap around. Get a new tree starting from the root
				navigator = this.tree.getNavigator(this.tree.getInput(), /*subTreeOnly*/true);
				prev = navigator.last();

				// This is complicated because .last will set the navigator to the last FileMatch,
				// so expand it and FF to its last child
				this.tree.expand(prev);
				let tmp;
				while (tmp = navigator.next()) {
					prev = tmp;
				}
			}

634 635 636 637 638 639
			if (!(prev instanceof Match)) {
				// There is a second non-Match result, which must be a collapsed FileMatch.
				// Expand it then select its last child.
				navigator.next();
				this.tree.expand(prev);
				prev = navigator.previous();
640 641 642 643
			}
		}

		// Reveal the newly selected element
644
		if (prev) {
R
Rob Lourens 已提交
645
			const eventPayload = { preventEditorOpen: true };
646 647 648
			this.tree.setFocus(prev, eventPayload);
			this.tree.setSelection([prev], eventPayload);
			this.tree.reveal(prev);
R
Rob Lourens 已提交
649
			this.selectCurrentMatchEmitter.fire();
650
		}
651 652
	}

653
	public setVisible(visible: boolean): void {
654
		this.viewletVisible.set(visible);
E
Erich Gamma 已提交
655
		if (visible) {
656 657 658 659 660 661
			if (this.changedWhileHidden) {
				// Render if results changed while viewlet was hidden - #37818
				this.refreshAndUpdateCount();
				this.changedWhileHidden = false;
			}

662
			super.setVisible(visible);
E
Erich Gamma 已提交
663 664 665
			this.tree.onVisible();
		} else {
			this.tree.onHidden();
666
			super.setVisible(visible);
E
Erich Gamma 已提交
667 668 669 670
		}

		// Enable highlights if there are searchresults
		if (this.viewModel) {
671
			this.viewModel.searchResult.toggleHighlights(visible);
E
Erich Gamma 已提交
672 673
		}

674
		// Open focused element from results in case the editor area is otherwise empty
B
Benjamin Pasero 已提交
675
		if (visible && !this.editorService.activeEditor) {
E
Erich Gamma 已提交
676 677
			let focus = this.tree.getFocus();
			if (focus) {
S
Sandeep Somavarapu 已提交
678
				this.onFocus(focus, true);
E
Erich Gamma 已提交
679 680 681 682
			}
		}
	}

Y
Yogesh 已提交
683 684 685 686
	public moveFocusToResults(): void {
		this.tree.domFocus();
	}

E
Erich Gamma 已提交
687 688 689
	public focus(): void {
		super.focus();

R
Rob Lourens 已提交
690 691 692 693 694
		const updatedText = this.updateTextFromSelection();
		this.searchWidget.focus(undefined, undefined, updatedText);
	}

	public updateTextFromSelection(allowUnselectedWord = true): boolean {
695
		let updatedText = false;
696 697
		const seedSearchStringFromSelection = this.configurationService.getValue<IEditorOptions>('editor').find.seedSearchStringFromSelection;
		if (seedSearchStringFromSelection) {
R
Rob Lourens 已提交
698
			let selectedText = this.getSearchTextFromEditor(allowUnselectedWord);
699
			if (selectedText) {
700 701 702 703
				if (this.searchWidget.searchInput.getRegex()) {
					selectedText = strings.escapeRegExpCharacters(selectedText);
				}

704
				this.searchWidget.searchInput.setValue(selectedText);
705
				updatedText = true;
706
			}
707
		}
R
Rob Lourens 已提交
708

R
Rob Lourens 已提交
709
		return updatedText;
710 711
	}

712 713
	public focusNextInputBox(): void {
		if (this.searchWidget.searchInputHasFocus()) {
714 715 716 717 718
			if (this.searchWidget.isReplaceShown()) {
				this.searchWidget.focus(true, true);
			} else {
				this.moveFocusFromSearchOrReplace();
			}
719 720 721 722
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
723
			this.moveFocusFromSearchOrReplace();
724 725 726 727
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
728 729 730 731 732 733
			this.inputPatternExcludes.focus();
			this.inputPatternExcludes.select();
			return;
		}

		if (this.inputPatternExcludes.inputHasFocus()) {
734 735 736 737 738
			this.selectTreeIfNotSelected();
			return;
		}
	}

739 740
	private moveFocusFromSearchOrReplace() {
		if (this.showsFileTypes()) {
741
			this.toggleQueryDetails(true, this.showsFileTypes());
742 743 744 745 746
		} else {
			this.selectTreeIfNotSelected();
		}
	}

747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
	public focusPreviousInputBox(): void {
		if (this.searchWidget.searchInputHasFocus()) {
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
			this.searchWidget.focus(true);
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
			this.searchWidget.focus(true, true);
			return;
		}

762 763 764 765 766 767
		if (this.inputPatternExcludes.inputHasFocus()) {
			this.inputPatternIncludes.focus();
			this.inputPatternIncludes.select();
			return;
		}

S
Sandeep Somavarapu 已提交
768 769 770 771
		if (this.tree.isDOMFocused()) {
			this.moveFocusFromResults();
			return;
		}
772 773
	}

S
Sandeep Somavarapu 已提交
774
	private moveFocusFromResults(): void {
775
		if (this.showsFileTypes()) {
776
			this.toggleQueryDetails(true, true, false, true);
777 778 779
		} else {
			this.searchWidget.focus(true, true);
		}
E
Erich Gamma 已提交
780 781 782 783 784 785 786
	}

	private reLayout(): void {
		if (this.isDisposed) {
			return;
		}

J
Joao Moreno 已提交
787 788 789 790 791
		if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
			dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
		} else {
			dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
		}
792

793
		this.searchWidget.setWidth(this.size.width - 28 /* container margin */);
E
Erich Gamma 已提交
794

795
		this.inputPatternExcludes.setWidth(this.size.width - 28 /* container margin */);
S
Sandeep Somavarapu 已提交
796
		this.inputPatternIncludes.setWidth(this.size.width - 28 /* container margin */);
E
Erich Gamma 已提交
797

798 799 800 801
		const messagesSize = this.messagesElement.style.display === 'none' ?
			0 :
			dom.getTotalHeight(this.messagesElement);

802 803
		const searchResultContainerSize = this.size.height -
			messagesSize -
804
			dom.getTotalHeight(this.searchWidgetsContainerElement);
805

806
		this.resultsElement.style.height = searchResultContainerSize + 'px';
E
Erich Gamma 已提交
807 808 809 810

		this.tree.layout(searchResultContainerSize);
	}

811
	public layout(dimension: dom.Dimension): void {
E
Erich Gamma 已提交
812
		this.size = dimension;
B
Benjamin Pasero 已提交
813
		this.reLayout();
E
Erich Gamma 已提交
814 815 816 817 818 819
	}

	public getControl(): ITree {
		return this.tree;
	}

S
Sandeep Somavarapu 已提交
820 821 822 823
	public isSearchSubmitted(): boolean {
		return this.searchSubmitted;
	}

824 825 826 827
	public isSearching(): boolean {
		return this.searching;
	}

S
Sandeep Somavarapu 已提交
828 829 830 831
	public hasSearchResults(): boolean {
		return !this.viewModel.searchResult.isEmpty();
	}

E
Erich Gamma 已提交
832
	public clearSearchResults(): void {
833
		this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
834
		this.showEmptyStage();
835
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
836
			this.showSearchWithoutFolderMessage();
837
		}
838
		this.searchWidget.clear();
S
Sandeep Somavarapu 已提交
839
		this.viewModel.cancelSearch();
E
Erich Gamma 已提交
840 841
	}

842
	public cancelSearch(): boolean {
S
Sandeep Somavarapu 已提交
843
		if (this.viewModel.cancelSearch()) {
844
			this.searchWidget.focus();
845 846 847 848 849
			return true;
		}
		return false;
	}

850
	private selectTreeIfNotSelected(): void {
E
Erich Gamma 已提交
851
		if (this.tree.getInput()) {
852
			this.tree.domFocus();
E
Erich Gamma 已提交
853 854 855 856 857 858 859
			let selection = this.tree.getSelection();
			if (selection.length === 0) {
				this.tree.focusNext();
			}
		}
	}

R
Rob Lourens 已提交
860
	private getSearchTextFromEditor(allowUnselectedWord: boolean): string {
B
Benjamin Pasero 已提交
861
		if (!this.editorService.activeEditor) {
E
Erich Gamma 已提交
862 863 864
			return null;
		}

R
Rob Lourens 已提交
865 866 867 868
		if (dom.isAncestor(document.activeElement, this.getContainer())) {
			return null;
		}

869 870 871 872
		let activeTextEditorWidget = this.editorService.activeTextEditorWidget;
		if (isDiffEditor(activeTextEditorWidget)) {
			if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) {
				activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor();
873
			} else {
874
				activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
875 876 877
			}
		}

878
		if (!isCodeEditor(activeTextEditorWidget)) {
E
Erich Gamma 已提交
879 880 881
			return null;
		}

882
		const range = activeTextEditorWidget.getSelection();
R
Rob Lourens 已提交
883 884 885 886
		if (!range) {
			return null;
		}

R
Rob Lourens 已提交
887
		if (range.isEmpty() && !this.searchWidget.searchInput.getValue() && allowUnselectedWord) {
888
			const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition());
R
Rob Lourens 已提交
889 890 891 892 893 894
			if (wordAtPosition) {
				return wordAtPosition.word;
			}
		}

		if (!range.isEmpty() && range.startLineNumber === range.endLineNumber) {
895
			let searchText = activeTextEditorWidget.getModel().getLineContent(range.startLineNumber);
896 897
			searchText = searchText.substring(range.startColumn - 1, range.endColumn - 1);
			return searchText;
E
Erich Gamma 已提交
898
		}
899

E
Erich Gamma 已提交
900 901 902 903 904 905 906
		return null;
	}

	private showsFileTypes(): boolean {
		return dom.hasClass(this.queryDetails, 'more');
	}

S
Sandeep Somavarapu 已提交
907 908
	public toggleCaseSensitive(): void {
		this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive());
909
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
910 911 912 913
	}

	public toggleWholeWords(): void {
		this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords());
914
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
915 916 917 918
	}

	public toggleRegex(): void {
		this.searchWidget.searchInput.setRegex(!this.searchWidget.searchInput.getRegex());
919
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
920 921
	}

922
	public toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean, reverse?: boolean): void {
E
Erich Gamma 已提交
923 924
		let cls = 'more';
		show = typeof show === 'undefined' ? !dom.hasClass(this.queryDetails, cls) : Boolean(show);
B
Benjamin Pasero 已提交
925
		this.viewletState['query.queryDetailsExpanded'] = show;
E
Erich Gamma 已提交
926 927 928
		skipLayout = Boolean(skipLayout);

		if (show) {
929
			this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true');
E
Erich Gamma 已提交
930
			dom.addClass(this.queryDetails, cls);
931
			if (moveFocus) {
932 933 934 935 936 937 938
				if (reverse) {
					this.inputPatternExcludes.focus();
					this.inputPatternExcludes.select();
				} else {
					this.inputPatternIncludes.focus();
					this.inputPatternIncludes.select();
				}
939
			}
E
Erich Gamma 已提交
940
		} else {
941
			this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'false');
E
Erich Gamma 已提交
942
			dom.removeClass(this.queryDetails, cls);
943
			if (moveFocus) {
944
				this.searchWidget.focus();
945
			}
E
Erich Gamma 已提交
946
		}
B
Benjamin Pasero 已提交
947

E
Erich Gamma 已提交
948 949 950 951 952
		if (!skipLayout && this.size) {
			this.layout(this.size);
		}
	}

953
	public searchInFolders(resources: URI[], pathToRelative: (from: string, to: string) => string): void {
954
		const folderPaths: string[] = [];
955
		const workspace = this.contextService.getWorkspace();
956 957 958 959 960 961 962 963 964 965 966 967 968

		if (resources) {
			resources.forEach(resource => {
				let folderPath: string;
				if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
					// Show relative path from the root for single-root mode
					folderPath = paths.normalize(pathToRelative(workspace.folders[0].uri.fsPath, resource.fsPath));
					if (folderPath && folderPath !== '.') {
						folderPath = './' + folderPath;
					}
				} else {
					const owningFolder = this.contextService.getWorkspaceFolder(resource);
					if (owningFolder) {
969
						const owningRootName = owningFolder.name;
970 971

						// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
972
						const isUniqueFolder = workspace.folders.filter(folder => folder.name === owningRootName).length === 1;
973
						if (isUniqueFolder) {
974 975 976 977 978 979
							const relativePath = paths.normalize(pathToRelative(owningFolder.uri.fsPath, resource.fsPath));
							if (relativePath === '.') {
								folderPath = `./${owningFolder.name}`;
							} else {
								folderPath = `./${owningFolder.name}/${relativePath}`;
							}
980 981 982
						} else {
							folderPath = resource.fsPath;
						}
983 984
					}
				}
985 986 987 988 989

				if (folderPath) {
					folderPaths.push(folderPath);
				}
			});
S
Sandeep Somavarapu 已提交
990 991
		}

992
		if (!folderPaths.length || folderPaths.some(folderPath => folderPath === '.')) {
S
Sandeep Somavarapu 已提交
993 994 995 996 997
			this.inputPatternIncludes.setValue('');
			this.searchWidget.focus();
			return;
		}

998
		// Show 'files to include' box
E
Erich Gamma 已提交
999
		if (!this.showsFileTypes()) {
1000
			this.toggleQueryDetails(true, true);
E
Erich Gamma 已提交
1001
		}
B
Benjamin Pasero 已提交
1002

1003
		this.inputPatternIncludes.setValue(folderPaths.join(', '));
B
Benjamin Pasero 已提交
1004
		this.searchWidget.focus(false);
E
Erich Gamma 已提交
1005 1006
	}

1007
	public onQueryChanged(preserveFocus?: boolean): void {
1008 1009 1010 1011
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1012 1013 1014
		const excludePatternText = this.inputPatternExcludes.getValue().trim();
		const includePatternText = this.inputPatternIncludes.getValue().trim();
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
E
Erich Gamma 已提交
1015 1016 1017 1018 1019

		if (contentPattern.length === 0) {
			return;
		}

1020
		// Validate regex is OK
E
Erich Gamma 已提交
1021 1022 1023 1024 1025
		if (isRegex) {
			let regExp: RegExp;
			try {
				regExp = new RegExp(contentPattern);
			} catch (e) {
1026
				return; // malformed regex
E
Erich Gamma 已提交
1027
			}
1028

E
Erich Gamma 已提交
1029
			if (strings.regExpLeadsToEndlessLoop(regExp)) {
1030
				return; // endless regex
E
Erich Gamma 已提交
1031 1032 1033
			}
		}

1034
		const content: IPatternInfo = {
E
Erich Gamma 已提交
1035 1036 1037
			pattern: contentPattern,
			isRegExp: isRegex,
			isCaseSensitive: isCaseSensitive,
1038
			isWordMatch: isWholeWords,
1039
			isSmartCase: this.configurationService.getValue<ISearchConfiguration>().search.smartCase
E
Erich Gamma 已提交
1040 1041
		};

1042 1043
		const excludePattern = this.inputPatternExcludes.getValue();
		const includePattern = this.inputPatternIncludes.getValue();
E
Erich Gamma 已提交
1044

R
Rob Lourens 已提交
1045 1046 1047
		// Need the full match line to correctly calculate replace text, if this is a search/replace with regex group references ($1, $2, ...).
		// 10000 chars is enough to avoid sending huge amounts of text around, if you do a replace with a longer match, it may or may not resolve the group refs correctly.
		// https://github.com/Microsoft/vscode/issues/58374
R
Rob Lourens 已提交
1048
		const charsPerLine = content.isRegExp ? 10000 :
1049
			250;
R
Rob Lourens 已提交
1050

1051
		const options: ITextQueryBuilderOptions = {
R
Rob Lourens 已提交
1052
			_reason: 'searchView',
1053
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
1054
			maxResults: SearchView.MAX_TEXT_RESULTS,
R
Rob Lourens 已提交
1055 1056
			disregardIgnoreFiles: !useExcludesAndIgnoreFiles || undefined,
			disregardExcludeSettings: !useExcludesAndIgnoreFiles || undefined,
R
Rob Lourens 已提交
1057
			excludePattern,
1058 1059
			includePattern,
			previewOptions: {
R
Rob Lourens 已提交
1060 1061
				matchLines: 1,
				charsPerLine
1062
			}
E
Erich Gamma 已提交
1063
		};
S
Sandeep Somavarapu 已提交
1064
		const folderResources = this.contextService.getWorkspace().folders;
1065 1066 1067 1068 1069 1070

		const onQueryValidationError = (err: Error) => {
			this.searchWidget.searchInput.showMessage({ content: err.message, type: MessageType.ERROR });
			this.viewModel.searchResult.clear();
		};

1071
		let query: ITextQuery;
1072
		try {
1073
			query = this.queryBuilder.text(content, folderResources.map(folder => folder.uri), options);
1074 1075
		} catch (err) {
			onQueryValidationError(err);
1076
			return;
1077
		}
1078

1079
		this.validateQuery(query).then(() => {
1080
			this.onQueryTriggered(query, options, excludePatternText, includePatternText);
1081

1082 1083 1084
			if (!preserveFocus) {
				this.searchWidget.focus(false); // focus back to input field
			}
1085
		}, onQueryValidationError);
1086 1087
	}

1088
	private validateQuery(query: ITextQuery): Thenable<void> {
1089 1090 1091 1092 1093 1094
		// Validate folderQueries
		const folderQueriesExistP =
			query.folderQueries.map(fq => {
				return this.fileService.existsFile(fq.folder);
			});

1095
		return Promise.resolve(folderQueriesExistP).then(existResults => {
R
Rob Lourens 已提交
1096 1097
			// If no folders exist, show an error message about the first one
			const existingFolderQueries = query.folderQueries.filter((folderQuery, i) => existResults[i]);
1098
			if (!query.folderQueries.length || existingFolderQueries.length) {
R
Rob Lourens 已提交
1099 1100 1101
				query.folderQueries = existingFolderQueries;
			} else {
				const nonExistantPath = query.folderQueries[0].folder.fsPath;
1102
				const searchPathNotFoundError = nls.localize('searchPathNotFoundError', "Search path not found: {0}", nonExistantPath);
1103
				return Promise.reject(new Error(searchPathNotFoundError));
1104 1105 1106 1107
			}

			return undefined;
		});
E
Erich Gamma 已提交
1108 1109
	}

1110
	private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): void {
R
Rob Lourens 已提交
1111
		this.searchWidget.searchInput.onSearchSubmit();
1112
		this.inputPatternExcludes.onSearchSubmit();
1113 1114
		this.inputPatternIncludes.onSearchSubmit();

S
Sandeep Somavarapu 已提交
1115 1116
		this.viewModel.cancelSearch();

1117 1118 1119 1120 1121 1122 1123
		// Progress total is 100.0% for more progress bar granularity
		let progressTotal = 1000;
		let progressWorked = 0;

		let progressRunner = query.useRipgrep ?
			this.progressService.show(/*infinite=*/true) :
			this.progressService.show(progressTotal);
E
Erich Gamma 已提交
1124

1125
		this.searchWidget.searchInput.clearMessage();
1126 1127 1128 1129 1130 1131
		this.searching = true;
		setTimeout(() => {
			if (this.searching) {
				this.changeActionAtPosition(0, this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL));
			}
		}, 2000);
E
Erich Gamma 已提交
1132 1133
		this.showEmptyStage();

1134
		let onComplete = (completed?: ISearchComplete) => {
1135 1136
			this.searching = false;
			this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1137 1138 1139 1140 1141 1142 1143 1144

			// Complete up to 100% as needed
			if (completed && !query.useRipgrep) {
				progressRunner.worked(progressTotal - progressWorked);
				setTimeout(() => progressRunner.done(), 200);
			} else {
				progressRunner.done();
			}
E
Erich Gamma 已提交
1145

1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
			// Do final render, then expand if just 1 file with less than 50 matches
			this.onSearchResultsChanged().then(() => {
				if (this.viewModel.searchResult.count() === 1) {
					const onlyMatch = this.viewModel.searchResult.matches()[0];
					if (onlyMatch.count() < 50) {
						return this.tree.expand(onlyMatch);
					}
				}

				return null;
1156
			});
1157

J
Johannes Rieken 已提交
1158
			this.viewModel.replaceString = this.searchWidget.getReplaceValue();
E
Erich Gamma 已提交
1159

1160
			let hasResults = !this.viewModel.searchResult.isEmpty();
E
Erich Gamma 已提交
1161

S
Sandeep Somavarapu 已提交
1162 1163
			this.searchSubmitted = true;
			this.updateActions();
E
Erich Gamma 已提交
1164

1165
			if (completed && completed.limitHit) {
1166
				this.searchWidget.searchInput.showMessage({
E
Erich Gamma 已提交
1167 1168 1169 1170 1171 1172
					content: nls.localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Please be more specific in your search to narrow down the results."),
					type: MessageType.WARNING
				});
			}

			if (!hasResults) {
1173 1174
				let hasExcludes = !!excludePatternText;
				let hasIncludes = !!includePatternText;
E
Erich Gamma 已提交
1175 1176
				let message: string;

1177 1178 1179
				if (!completed) {
					message = nls.localize('searchCanceled', "Search was canceled before any results could be found - ");
				} else if (hasIncludes && hasExcludes) {
R
Rob Lourens 已提交
1180
					message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText);
E
Erich Gamma 已提交
1181
				} else if (hasIncludes) {
R
Rob Lourens 已提交
1182
					message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText);
E
Erich Gamma 已提交
1183
				} else if (hasExcludes) {
R
Rob Lourens 已提交
1184
					message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText);
E
Erich Gamma 已提交
1185
				} else {
1186
					message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and ignore files - ");
E
Erich Gamma 已提交
1187 1188
				}

1189 1190 1191
				// Indicate as status to ARIA
				aria.status(message);

E
Erich Gamma 已提交
1192
				this.tree.onHidden();
1193
				dom.hide(this.resultsElement);
1194 1195

				const messageEl = this.clearMessage();
1196
				const p = dom.append(messageEl, $('p', undefined, message));
E
Erich Gamma 已提交
1197

1198
				if (!completed) {
1199
					const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again")));
1200
					this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
1201
						dom.EventHelper.stop(e, false);
1202
						this.onQueryChanged();
1203
					}));
1204
				} else if (hasIncludes || hasExcludes) {
1205
					const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files")));
1206
					this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
E
Erich Gamma 已提交
1207 1208
						dom.EventHelper.stop(e, false);

1209
						this.inputPatternExcludes.setValue('');
E
Erich Gamma 已提交
1210 1211
						this.inputPatternIncludes.setValue('');

1212
						this.onQueryChanged();
1213
					}));
E
Erich Gamma 已提交
1214
				} else {
1215
					const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings")));
1216
					this.addClickEvents(openSettingsLink, this.onOpenSettings);
E
Erich Gamma 已提交
1217
				}
1218 1219

				if (completed) {
1220
					dom.append(p, $('span', undefined, ' - '));
1221

1222
					const learnMoreLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.learnMore', "Learn More")));
1223
					this.addClickEvents(learnMoreLink, this.onLearnMore);
1224
				}
1225

1226
				if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
1227
					this.showSearchWithoutFolderMessage();
1228
				}
E
Erich Gamma 已提交
1229
			} else {
R
Rob Lourens 已提交
1230
				this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights
1231

1232
				// Indicate final search result count for ARIA
1233
				aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount()));
E
Erich Gamma 已提交
1234 1235 1236 1237
			}
		};

		let onError = (e: any) => {
1238 1239 1240
			if (errors.isPromiseCanceledError(e)) {
				onComplete(null);
			} else {
1241 1242
				this.searching = false;
				this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1243
				progressRunner.done();
1244
				this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR });
1245
				this.viewModel.searchResult.clear();
1246

1247
				if (e.code === SearchErrorCode.unknownEncoding && !this.configurationService.getValue('search.useLegacySearch')) {
1248 1249 1250 1251 1252
					this.notificationService.prompt(Severity.Info, nls.localize('otherEncodingWarning', "You can enable \"search.useLegacySearch\" to search non-standard file encodings."),
						[{
							label: nls.localize('otherEncodingWarning.openSettingsLabel', "Open Settings"),
							run: () => this.openSettings('search.useLegacySearch')
						}]);
1253 1254 1255 1256 1257 1258 1259
				} else if (e.code === SearchErrorCode.regexParseError && !this.configurationService.getValue('search.usePCRE2')) {
					// If the regex parsed in JS but not rg, it likely uses features that are supported in JS and PCRE2 but not Rust
					this.notificationService.prompt(Severity.Info, nls.localize('rgRegexError', "You can enable \"search.usePCRE2\" to enable some extra regex features like lookbehind and backreferences."),
						[{
							label: nls.localize('otherEncodingWarning.openSettingsLabel', "Open Settings"),
							run: () => this.openSettings('search.usePCRE2')
						}]);
1260
				}
E
Erich Gamma 已提交
1261 1262 1263
			}
		};

1264 1265
		let total: number = 0;
		let worked: number = 0;
E
Erich Gamma 已提交
1266
		let visibleMatches = 0;
1267 1268 1269 1270 1271 1272 1273 1274 1275
		let onProgress = (p: ISearchProgressItem) => {
			// Progress
			if (p.total) {
				total = p.total;
			}
			if (p.worked) {
				worked = p.worked;
			}
		};
E
Erich Gamma 已提交
1276 1277

		// Handle UI updates in an interval to show frequent progress and results
1278
		let uiRefreshHandle: any = setInterval(() => {
1279
			if (!this.searching) {
E
Erich Gamma 已提交
1280 1281 1282 1283
				window.clearInterval(uiRefreshHandle);
				return;
			}

1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307
			if (!query.useRipgrep) {
				// Progress bar update
				let fakeProgress = true;
				if (total > 0 && worked > 0) {
					let ratio = Math.round((worked / total) * progressTotal);
					if (ratio > progressWorked) { // never show less progress than what we have already
						progressRunner.worked(ratio - progressWorked);
						progressWorked = ratio;
						fakeProgress = false;
					}
				}

				// Fake progress up to 90%, or when actual progress beats it
				const fakeMax = 900;
				const fakeMultiplier = 12;
				if (fakeProgress && progressWorked < fakeMax) {
					// Linearly decrease the rate of fake progress.
					// 1 is the smallest allowed amount of progress.
					const fakeAmt = Math.round((fakeMax - progressWorked) / fakeMax * fakeMultiplier) || 1;
					progressWorked += fakeAmt;
					progressRunner.worked(fakeAmt);
				}
			}

E
Erich Gamma 已提交
1308
			// Search result tree update
1309 1310 1311
			const fileCount = this.viewModel.searchResult.fileCount();
			if (visibleMatches !== fileCount) {
				visibleMatches = fileCount;
1312
				this.tree.refresh();
1313

1314
				this.updateSearchResultCount(options.disregardExcludeSettings);
1315
			}
1316
			if (fileCount > 0) {
S
Sandeep Somavarapu 已提交
1317
				this.updateActions();
E
Erich Gamma 已提交
1318
			}
1319
		}, 100);
E
Erich Gamma 已提交
1320

S
Sandeep Somavarapu 已提交
1321
		this.searchWidget.setReplaceAllActionState(false);
1322

1323
		this.viewModel.search(query, onProgress).then(onComplete, onError);
E
Erich Gamma 已提交
1324 1325
	}

1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
	private addClickEvents = (element: HTMLElement, handler: (event: any) => void): void => {
		this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.CLICK, handler));
		this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
			let event = new StandardKeyboardEvent(e as KeyboardEvent);
			let eventHandled = true;

			if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
				handler(e);
			} else {
				eventHandled = false;
			}

			if (eventHandled) {
				event.preventDefault();
				event.stopPropagation();
			}
		}));
	}

	private onOpenSettings = (e: dom.EventLike): void => {
		dom.EventHelper.stop(e, false);

1348 1349 1350
		this.openSettings('.exclude');
	}

1351
	private openSettings(query: string): Thenable<IEditor> {
1352 1353
		const options: ISettingsEditorOptions = { query };
		return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ?
1354 1355 1356 1357 1358 1359 1360 1361 1362 1363
			this.preferencesService.openWorkspaceSettings(undefined, options) :
			this.preferencesService.openGlobalSettings(undefined, options);
	}

	private onLearnMore = (e: MouseEvent): void => {
		dom.EventHelper.stop(e, false);

		window.open('https://go.microsoft.com/fwlink/?linkid=853977');
	}

1364
	private updateSearchResultCount(disregardExcludesAndIgnores?: boolean): void {
1365
		const fileCount = this.viewModel.searchResult.fileCount();
1366 1367
		this.hasSearchResultsKey.set(fileCount > 0);

1368
		const msgWasHidden = this.messagesElement.style.display === 'none';
1369
		if (fileCount > 0) {
1370
			const messageEl = this.clearMessage();
1371 1372 1373 1374 1375 1376
			let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount);
			if (disregardExcludesAndIgnores) {
				resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled");
			}

			dom.append(messageEl, $('p', undefined, resultMsg));
1377 1378 1379
			if (msgWasHidden) {
				this.reLayout();
			}
1380
		} else if (!msgWasHidden) {
1381
			dom.hide(this.messagesElement);
1382 1383 1384
		}
	}

1385 1386 1387 1388 1389 1390 1391
	private buildResultCountMessage(resultCount: number, fileCount: number): string {
		if (resultCount === 1 && fileCount === 1) {
			return nls.localize('search.file.result', "{0} result in {1} file", resultCount, fileCount);
		} else if (resultCount === 1) {
			return nls.localize('search.files.result', "{0} result in {1} files", resultCount, fileCount);
		} else if (fileCount === 1) {
			return nls.localize('search.file.results', "{0} results in {1} file", resultCount, fileCount);
1392
		} else {
1393
			return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount);
1394 1395 1396
		}
	}

1397 1398 1399 1400
	private showSearchWithoutFolderMessage(): void {
		this.searchWithoutFolderMessageElement = this.clearMessage();

		const textEl = dom.append(this.searchWithoutFolderMessageElement,
1401
			$('p', undefined, nls.localize('searchWithoutFolder', "You have not yet opened a folder. Only open files are currently searched - ")));
1402 1403

		const openFolderLink = dom.append(textEl,
1404
			$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
1405 1406 1407 1408 1409 1410

		this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => {
			dom.EventHelper.stop(e, false);

			const actionClass = env.isMacintosh ? OpenFileFolderAction : OpenFolderAction;
			const action = this.instantiationService.createInstance<string, string, IAction>(actionClass, actionClass.ID, actionClass.LABEL);
1411
			this.actionRunner.run(action).then(() => {
1412 1413 1414 1415
				action.dispose();
			}, err => {
				action.dispose();
				errors.onUnexpectedError(err);
1416
			});
1417
		}));
1418 1419
	}

E
Erich Gamma 已提交
1420
	private showEmptyStage(): void {
1421

E
Erich Gamma 已提交
1422
		// disable 'result'-actions
S
Sandeep Somavarapu 已提交
1423 1424
		this.searchSubmitted = false;
		this.updateActions();
E
Erich Gamma 已提交
1425 1426

		// clean up ui
S
Sandeep Somavarapu 已提交
1427
		// this.replaceService.disposeAllReplacePreviews();
1428
		dom.hide(this.messagesElement);
1429
		dom.show(this.resultsElement);
E
Erich Gamma 已提交
1430
		this.tree.onVisible();
S
Sandeep Somavarapu 已提交
1431
		this.currentSelectedFileMatch = null;
E
Erich Gamma 已提交
1432 1433
	}

1434
	private onFocus(lineMatch: any, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Thenable<any> {
E
Erich Gamma 已提交
1435
		if (!(lineMatch instanceof Match)) {
1436
			this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
1437
			return Promise.resolve(true);
E
Erich Gamma 已提交
1438 1439
		}

1440 1441
		const useReplacePreview = this.configurationService.getValue<ISearchConfiguration>().search.useReplacePreview;
		return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
R
Rob Lourens 已提交
1442 1443
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1444 1445
	}

1446
	public open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Thenable<any> {
R
Rob Lourens 已提交
1447 1448
		const selection = this.getSelectionFrom(element);
		const resource = element instanceof Match ? element.parent().resource() : (<FileMatch>element).resource();
E
Erich Gamma 已提交
1449
		return this.editorService.openEditor({
1450
			resource: resource,
E
Erich Gamma 已提交
1451
			options: {
1452 1453
				preserveFocus,
				pinned,
S
Sandeep Somavarapu 已提交
1454
				selection,
1455
				revealIfVisible: true
E
Erich Gamma 已提交
1456
			}
B
Benjamin Pasero 已提交
1457
		}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => {
S
Sandeep Somavarapu 已提交
1458
			if (editor && element instanceof Match && preserveFocus) {
1459 1460 1461 1462
				this.viewModel.searchResult.rangeHighlightDecorations.highlightRange(
					(<ICodeEditor>editor.getControl()).getModel(),
					element.range()
				);
S
Sandeep Somavarapu 已提交
1463
			} else {
1464
				this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
S
Sandeep Somavarapu 已提交
1465
			}
R
Rob Lourens 已提交
1466

R
Rob Lourens 已提交
1467 1468 1469
			if (editor) {
				return this.editorGroupsService.activateGroup(editor.group);
			} else {
1470
				return Promise.resolve(null);
R
Rob Lourens 已提交
1471
			}
S
Sandeep Somavarapu 已提交
1472
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1473 1474
	}

1475
	private getSelectionFrom(element: FileMatchOrMatch): any {
1476
		let match: Match | null = null;
1477
		if (element instanceof Match) {
J
Johannes Rieken 已提交
1478
			match = element;
1479 1480
		}
		if (element instanceof FileMatch && element.count() > 0) {
J
Johannes Rieken 已提交
1481
			match = element.matches()[element.matches().length - 1];
1482 1483
		}
		if (match) {
J
Johannes Rieken 已提交
1484
			let range = match.range();
S
Sandeep Somavarapu 已提交
1485
			if (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) {
J
Johannes Rieken 已提交
1486
				let replaceString = match.replaceString;
1487 1488
				return {
					startLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1489
					startColumn: range.startColumn,
1490
					endLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1491
					endColumn: range.startColumn + replaceString.length
1492 1493 1494 1495 1496 1497 1498
				};
			}
			return range;
		}
		return void 0;
	}

B
Benjamin Pasero 已提交
1499
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1500 1501 1502 1503
		if (!this.viewModel) {
			return;
		}

1504
		// remove search results from this resource as it got disposed
B
Benjamin Pasero 已提交
1505
		if (!this.untitledEditorService.isDirty(resource)) {
1506 1507
			let matches = this.viewModel.searchResult.matches();
			for (let i = 0, len = matches.length; i < len; i++) {
B
Benjamin Pasero 已提交
1508
				if (resource.toString() === matches[i].resource().toString()) {
1509 1510
					this.viewModel.searchResult.remove(matches[i]);
				}
E
Erich Gamma 已提交
1511 1512 1513 1514 1515 1516 1517 1518 1519
			}
		}
	}

	private onFilesChanged(e: FileChangesEvent): void {
		if (!this.viewModel) {
			return;
		}

1520
		let matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1521 1522 1523

		for (let i = 0, len = matches.length; i < len; i++) {
			if (e.contains(matches[i].resource(), FileChangeType.DELETED)) {
1524
				this.viewModel.searchResult.remove(matches[i]);
E
Erich Gamma 已提交
1525 1526 1527 1528 1529
			}
		}
	}

	public getActions(): IAction[] {
S
Sandeep Somavarapu 已提交
1530
		return this.actions;
E
Erich Gamma 已提交
1531 1532
	}

1533
	private changeActionAtPosition(index: number, newAction: ClearSearchResultsAction | CancelSearchAction | RefreshAction | CollapseDeepestExpandedLevelAction): void {
1534 1535 1536 1537
		this.actions.splice(index, 1, newAction);
		this.updateTitleArea();
	}

1538
	private clearHistory(): void {
1539 1540 1541 1542 1543
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

B
Benjamin Pasero 已提交
1544
	protected saveState(): void {
A
Amy Qiu 已提交
1545 1546 1547 1548
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1549
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1550
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1551
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1552

B
Benjamin Pasero 已提交
1553 1554 1555 1556 1557 1558 1559
		this.viewletState['query.contentPattern'] = contentPattern;
		this.viewletState['query.regex'] = isRegex;
		this.viewletState['query.wholeWords'] = isWholeWords;
		this.viewletState['query.caseSensitive'] = isCaseSensitive;
		this.viewletState['query.folderExclusions'] = patternExcludes;
		this.viewletState['query.folderIncludes'] = patternIncludes;
		this.viewletState['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles;
A
Amy Qiu 已提交
1560

B
Benjamin Pasero 已提交
1561 1562 1563
		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		this.viewletState['view.showReplace'] = isReplaceShown;

1564 1565
		const history: ISearchHistoryValues = Object.create(null);

1566
		const searchHistory = this.searchWidget.getSearchHistory();
1567 1568 1569 1570
		if (searchHistory && searchHistory.length) {
			history.search = searchHistory;
		}

1571
		const replaceHistory = this.searchWidget.getReplaceHistory();
1572 1573 1574 1575
		if (replaceHistory && replaceHistory.length) {
			history.replace = replaceHistory;
		}

1576
		const patternExcludesHistory = this.inputPatternExcludes.getHistory();
1577 1578 1579 1580
		if (patternExcludesHistory && patternExcludesHistory.length) {
			history.exclude = patternExcludesHistory;
		}

1581
		const patternIncludesHistory = this.inputPatternIncludes.getHistory();
1582 1583 1584
		if (patternIncludesHistory && patternIncludesHistory.length) {
			history.include = patternIncludesHistory;
		}
1585

1586
		this.searchHistoryService.save(history);
B
Benjamin Pasero 已提交
1587 1588

		super.saveState();
A
Amy Qiu 已提交
1589 1590
	}

E
Erich Gamma 已提交
1591 1592 1593 1594 1595
	public dispose(): void {
		this.isDisposed = true;

		super.dispose();
	}
1596 1597 1598
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
B
Benjamin Pasero 已提交
1599
	const matchHighlightColor = theme.getColor(editorFindMatchHighlight);
1600
	if (matchHighlightColor) {
I
isidor 已提交
1601
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`);
B
Benjamin Pasero 已提交
1602 1603 1604 1605
	}

	const diffInsertedColor = theme.getColor(diffInserted);
	if (diffInsertedColor) {
I
isidor 已提交
1606
		collector.addRule(`.monaco-workbench .search-view .replaceMatch { background-color: ${diffInsertedColor}; }`);
B
Benjamin Pasero 已提交
1607 1608 1609 1610
	}

	const diffRemovedColor = theme.getColor(diffRemoved);
	if (diffRemovedColor) {
I
isidor 已提交
1611
		collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { background-color: ${diffRemovedColor}; }`);
B
Benjamin Pasero 已提交
1612 1613 1614 1615
	}

	const diffInsertedOutlineColor = theme.getColor(diffInsertedOutline);
	if (diffInsertedOutlineColor) {
1616
		collector.addRule(`.monaco-workbench .search-view .replaceMatch:not(:empty) { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${diffInsertedOutlineColor}; }`);
B
Benjamin Pasero 已提交
1617 1618 1619 1620
	}

	const diffRemovedOutlineColor = theme.getColor(diffRemovedOutline);
	if (diffRemovedOutlineColor) {
1621
		collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${diffRemovedOutlineColor}; }`);
B
Benjamin Pasero 已提交
1622 1623
	}

1624 1625
	const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
	if (findMatchHighlightBorder) {
1626
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findMatchHighlightBorder}; }`);
1627
	}
1628 1629 1630 1631 1632

	const outlineSelectionColor = theme.getColor(listActiveSelectionForeground);
	if (outlineSelectionColor) {
		collector.addRule(`.monaco-workbench .search-view .monaco-tree.focused .monaco-tree-row.focused.selected:not(.highlighted) .action-label:focus { outline-color: ${outlineSelectionColor} }`);
	}
1633
});