searchView.ts 58.8 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
9
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
10
import * as aria from 'vs/base/browser/ui/aria/aria';
11
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
12 13 14
import { IAction } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
15
import { anyEvent, debounceEvent, Emitter } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
16
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
17
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
18 19 20
import * as paths from 'vs/base/common/paths';
import * as env from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
21
import { URI } from 'vs/base/common/uri';
22
import { TPromise } from 'vs/base/common/winjs.base';
S
Sandeep Somavarapu 已提交
23
import { ITree } from 'vs/base/parts/tree/browser/tree';
24
import 'vs/css!./media/searchview';
25 26 27
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 已提交
28
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
29
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
J
Johannes Rieken 已提交
30
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
31 32
import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
33
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
34 35
import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService';
import { INotificationService } from 'vs/platform/notification/common/notification';
J
Johannes Rieken 已提交
36
import { IProgressService } from 'vs/platform/progress/common/progress';
S
Sandeep Somavarapu 已提交
37
import { IPatternInfo, IQueryOptions, ISearchComplete, ISearchConfiguration, ISearchHistoryService, ISearchProgressItem, ISearchQuery, VIEW_ID } from 'vs/platform/search/common/search';
38
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
J
Johannes Rieken 已提交
39
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
40
import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
41 42 43
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';
44
import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd';
45 46
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { Scope } from 'vs/workbench/common/memento';
47 48
import { IPanel } from 'vs/workbench/common/panel';
import { IViewlet } from 'vs/workbench/common/viewlet';
49 50 51 52 53 54 55 56 57 58
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';
import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder';
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 已提交
59
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
S
Sandeep Somavarapu 已提交
60
import { IPartService } from 'vs/workbench/services/part/common/partService';
61
import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
62
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
E
Erich Gamma 已提交
63

64 65
const $ = dom.$;

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

68 69
	private static readonly MAX_TEXT_RESULTS = 10000;
	private static readonly SHOW_REPLACE_STORAGE_KEY = 'vs.search.show.replace';
S
Sandeep Somavarapu 已提交
70

71 72 73
	private static readonly WIDE_CLASS_NAME = 'wide';
	private static readonly WIDE_VIEW_SIZE = 600;

E
Erich Gamma 已提交
74
	private isDisposed: boolean;
75

E
Erich Gamma 已提交
76
	private queryBuilder: QueryBuilder;
77
	private viewModel: SearchModel;
E
Erich Gamma 已提交
78

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

S
Sandeep Somavarapu 已提交
92
	private searchSubmitted: boolean;
93
	private searching: boolean;
S
Sandeep Somavarapu 已提交
94

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

S
Sandeep Somavarapu 已提交
109 110
	private currentSelectedFileMatch: FileMatch;

M
Matt Bierner 已提交
111
	private readonly selectCurrentMatchEmitter: Emitter<string>;
S
Sandeep Somavarapu 已提交
112
	private delayedRefresh: Delayer<void>;
113
	private changedWhileHidden: boolean;
114
	private isWide: boolean;
R
Rob Lourens 已提交
115

116
	private searchWithoutFolderMessageElement: HTMLElement;
117

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

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

		this.queryBuilder = this.instantiationService.createInstance(QueryBuilder);
		this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE);

B
Benjamin Pasero 已提交
158 159 160
		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()));
161
		this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
162

R
Rob Lourens 已提交
163 164 165 166
		this.selectCurrentMatchEmitter = new Emitter<string>();
		debounceEvent(this.selectCurrentMatchEmitter.event, (l, e) => e, 100, /*leading=*/true)
			(() => this.selectCurrentMatch());

S
Sandeep Somavarapu 已提交
167
		this.delayedRefresh = new Delayer<void>(250);
E
Erich Gamma 已提交
168 169
	}

170
	private onDidChangeWorkbenchState(): void {
171 172
		if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.searchWithoutFolderMessageElement) {
			dom.hide(this.searchWithoutFolderMessageElement);
173 174 175
		}
	}

176
	public create(parent: HTMLElement): TPromise<void> {
E
Erich Gamma 已提交
177 178
		super.create(parent);

B
Benjamin Pasero 已提交
179
		this.viewModel = this._register(this.searchWorkbenchService.searchModel);
180
		const containerElement = dom.append(parent, $('.search-view'));
E
Erich Gamma 已提交
181

R
Rob Lourens 已提交
182
		this.searchWidgetsContainerElement = dom.append(containerElement, $('.search-widgets-container'));
183
		this.createSearchWidget(this.searchWidgetsContainerElement);
184

185
		const history = this.searchHistoryService.load();
186
		const filePatterns = this.viewletSettings['query.filePatterns'] || '';
R
Rob Lourens 已提交
187 188 189 190
		const patternExclusions = this.viewletSettings['query.folderExclusions'] || '';
		const patternExclusionsHistory: string[] = history.exclude || [];
		const patternIncludes = this.viewletSettings['query.folderIncludes'] || '';
		const patternIncludesHistory: string[] = history.include || [];
A
Amy Qiu 已提交
191
		const queryDetailsExpanded = this.viewletSettings['query.queryDetailsExpanded'] || '';
192 193
		const useExcludesAndIgnoreFiles = typeof this.viewletSettings['query.useExcludesAndIgnoreFiles'] === 'boolean' ?
			this.viewletSettings['query.useExcludesAndIgnoreFiles'] : true;
194

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

197 198 199
		// 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 已提交
200

201 202 203 204 205 206
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => {
			dom.EventHelper.stop(e);
			this.toggleQueryDetails();
		}));
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
			const event = new StandardKeyboardEvent(e);
E
Erich Gamma 已提交
207

208 209 210 211 212 213 214
			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 已提交
215

216 217 218 219 220 221 222 223 224
			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 已提交
225

226 227 228 229 230
		// 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));
231

232 233 234 235
		this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, {
			ariaLabel: nls.localize('label.includes', 'Search Include Patterns'),
			history: patternIncludesHistory,
		}));
236

237
		this.inputPatternIncludes.setValue(patternIncludes);
238

239
		this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true));
240 241 242 243 244 245 246 247 248 249 250
		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,
		}));
251

252 253
		this.inputPatternExcludes.setValue(patternExclusions);
		this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles);
254

255
		this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true));
256 257 258 259
		this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget
		this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused);

		this.messagesElement = dom.append(containerElement, $('.messages'));
260
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
261
			this.showSearchWithoutFolderMessage();
262
		}
263

264
		this.createSearchResultsView(containerElement);
265

S
Sandeep Somavarapu 已提交
266 267 268 269 270
		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)
		];
271

272
		if (filePatterns !== '' || patternExclusions !== '' || patternIncludes !== '' || queryDetailsExpanded !== '' || !useExcludesAndIgnoreFiles) {
273
			this.toggleQueryDetails(true, true, true);
274 275
		}

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

S
Sandeep Somavarapu 已提交
278 279 280
		this._register(this.onDidFocus(() => this.viewletFocused.set(true)));
		this._register(this.onDidBlur(() => this.viewletFocused.set(false)));

281 282 283
		return TPromise.as(null);
	}

J
Johannes Rieken 已提交
284
	public get searchAndReplaceWidget(): SearchWidget {
285 286 287
		return this.searchWidget;
	}

288 289 290 291
	public get searchIncludePattern(): PatternInputWidget {
		return this.inputPatternIncludes;
	}

292 293 294 295
	public get searchExcludePattern(): PatternInputWidget {
		return this.inputPatternExcludes;
	}

S
Sandeep Somavarapu 已提交
296 297 298 299 300 301
	private updateActions(): void {
		for (const action of this.actions) {
			action.update();
		}
	}

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

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

320
		if (this.storageService.getBoolean(SearchView.SHOW_REPLACE_STORAGE_KEY, StorageScope.WORKSPACE, true)) {
S
Sandeep Somavarapu 已提交
321 322 323
			this.searchWidget.toggleReplace(true);
		}

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

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

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

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

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

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

		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		if (!isReplaceShown) {
370
			this.storageService.store(SearchView.SHOW_REPLACE_STORAGE_KEY, false, StorageScope.WORKSPACE);
371
		} else {
372
			this.storageService.remove(SearchView.SHOW_REPLACE_STORAGE_KEY, StorageScope.WORKSPACE);
373
		}
S
Sandeep Somavarapu 已提交
374 375
	}

S
Sandeep Somavarapu 已提交
376
	private onSearchResultsChanged(event?: IChangeEvent): TPromise<any> {
377 378 379 380 381 382 383 384 385
		if (this.isVisible()) {
			return this.refreshAndUpdateCount(event);
		} else {
			this.changedWhileHidden = true;
			return TPromise.wrap(null);
		}
	}

	private refreshAndUpdateCount(event?: IChangeEvent): TPromise<void> {
S
Sandeep Somavarapu 已提交
386
		return this.refreshTree(event).then(() => {
S
Sandeep Somavarapu 已提交
387
			this.searchWidget.setReplaceAllActionState(!this.viewModel.searchResult.isEmpty());
388
			this.updateSearchResultCount();
S
Sandeep Somavarapu 已提交
389
		});
390 391
	}

S
Sandeep Somavarapu 已提交
392
	private refreshTree(event?: IChangeEvent): TPromise<any> {
393
		if (!event || event.added || event.removed) {
S
Sandeep Somavarapu 已提交
394
			return this.tree.refresh(this.viewModel.searchResult);
395 396
		} else {
			if (event.elements.length === 1) {
S
Sandeep Somavarapu 已提交
397
				return this.tree.refresh(event.elements[0]);
398
			} else {
S
Sandeep Somavarapu 已提交
399
				return this.tree.refresh(event.elements);
400 401 402 403
			}
		}
	}

404
	private replaceAll(): void {
405
		if (this.viewModel.searchResult.count() === 0) {
S
Sandeep Somavarapu 已提交
406 407 408
			return;
		}

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

J
Johannes Rieken 已提交
411 412 413
		let occurrences = this.viewModel.searchResult.count();
		let fileCount = this.viewModel.searchResult.fileCount();
		let replaceValue = this.searchWidget.getReplaceValue() || '';
414
		let afterReplaceAllMessage = this.buildAfterReplaceAllMessage(occurrences, fileCount, replaceValue);
415

B
Benjamin Pasero 已提交
416
		let confirmation: IConfirmation = {
417
			title: nls.localize('replaceAll.confirmation.title', "Replace All"),
418
			message: this.buildReplaceAllConfirmationMessage(occurrences, fileCount, replaceValue),
419
			primaryButton: nls.localize('replaceAll.confirm.button', "&&Replace"),
B
Benjamin Pasero 已提交
420
			type: 'question'
421 422
		};

423
		this.dialogService.confirm(confirmation).then(res => {
424
			if (res.confirmed) {
425 426 427
				this.searchWidget.setReplaceAllActionState(false);
				this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
					progressRunner.done();
428
					const messageEl = this.clearMessage();
429
					dom.append(messageEl, $('p', undefined, afterReplaceAllMessage));
430 431 432
				}, (error) => {
					progressRunner.done();
					errors.isPromiseCanceledError(error);
433
					this.notificationService.error(error);
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 496 497 498 499 500 501 502
	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);
	}

503 504
	private clearMessage(): HTMLElement {
		this.searchWithoutFolderMessageElement = void 0;
505

506 507 508 509 510
		dom.clearNode(this.messagesElement);
		dom.show(this.messagesElement);
		dispose(this.messageDisposables);
		this.messageDisposables = [];

511
		return dom.append(this.messagesElement, $('.message'));
512 513
	}

514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
	private createSearchResultsView(container: HTMLElement): void {
		this.resultsElement = dom.append(container, $('.results.show-file-icons'));
		const dataSource = this._register(this.instantiationService.createInstance(SearchDataSource));
		const renderer = this._register(this.instantiationService.createInstance(SearchRenderer, this.getActionRunner(), this));
		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 已提交
531
			}));
532

533 534 535 536 537 538 539 540
		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);
541
				}
542 543 544 545 546 547 548
				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 已提交
549

550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
		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 已提交
570 571
	}

R
Rob Lourens 已提交
572 573
	public selectCurrentMatch(): void {
		const focused = this.tree.getFocus();
574
		const eventPayload = { focusEditor: true };
R
Rob Lourens 已提交
575 576 577 578
		this.tree.setSelection([focused], eventPayload);
	}

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

R
Rob Lourens 已提交
581 582 583 584 585 586 587 588 589 590
		// 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();
591
		if (!next) {
R
Rob Lourens 已提交
592 593 594 595
			// 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();
596 597 598
		}

		// Expand and go past FileMatch nodes
R
Rob Lourens 已提交
599
		while (!(next instanceof Match)) {
600 601
			if (!this.tree.isExpanded(next)) {
				this.tree.expand(next);
602 603 604
			}

			// Select the FileMatch's first child
605
			next = navigator.next();
606 607 608
		}

		// Reveal the newly selected element
609 610 611 612 613 614 615
		if (next) {
			const eventPayload = { preventEditorOpen: true };
			this.tree.setFocus(next, eventPayload);
			this.tree.setSelection([next], eventPayload);
			this.tree.reveal(next);
			this.selectCurrentMatchEmitter.fire();
		}
616 617
	}

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

622
		let prev = navigator.previous();
623 624

		// Expand and go past FileMatch nodes
625 626
		if (!(prev instanceof Match)) {
			prev = navigator.previous();
R
Rob Lourens 已提交
627 628 629 630 631 632 633 634 635 636 637 638 639 640
			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;
				}
			}

641 642 643 644 645 646
			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();
647 648 649 650
			}
		}

		// Reveal the newly selected element
651
		if (prev) {
R
Rob Lourens 已提交
652
			const eventPayload = { preventEditorOpen: true };
653 654 655
			this.tree.setFocus(prev, eventPayload);
			this.tree.setSelection([prev], eventPayload);
			this.tree.reveal(prev);
R
Rob Lourens 已提交
656
			this.selectCurrentMatchEmitter.fire();
657
		}
658 659
	}

E
Erich Gamma 已提交
660 661
	public setVisible(visible: boolean): TPromise<void> {
		let promise: TPromise<void>;
662
		this.viewletVisible.set(visible);
E
Erich Gamma 已提交
663
		if (visible) {
664 665 666 667 668 669
			if (this.changedWhileHidden) {
				// Render if results changed while viewlet was hidden - #37818
				this.refreshAndUpdateCount();
				this.changedWhileHidden = false;
			}

E
Erich Gamma 已提交
670 671 672 673 674 675 676 677 678
			promise = super.setVisible(visible);
			this.tree.onVisible();
		} else {
			this.tree.onHidden();
			promise = super.setVisible(visible);
		}

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

682
		// Open focused element from results in case the editor area is otherwise empty
B
Benjamin Pasero 已提交
683
		if (visible && !this.editorService.activeEditor) {
E
Erich Gamma 已提交
684 685
			let focus = this.tree.getFocus();
			if (focus) {
S
Sandeep Somavarapu 已提交
686
				this.onFocus(focus, true);
E
Erich Gamma 已提交
687 688 689 690 691 692
			}
		}

		return promise;
	}

Y
Yogesh 已提交
693 694 695 696
	public moveFocusToResults(): void {
		this.tree.domFocus();
	}

E
Erich Gamma 已提交
697 698 699
	public focus(): void {
		super.focus();

R
Rob Lourens 已提交
700 701 702 703 704
		const updatedText = this.updateTextFromSelection();
		this.searchWidget.focus(undefined, undefined, updatedText);
	}

	public updateTextFromSelection(allowUnselectedWord = true): boolean {
705
		let updatedText = false;
706 707
		const seedSearchStringFromSelection = this.configurationService.getValue<IEditorOptions>('editor').find.seedSearchStringFromSelection;
		if (seedSearchStringFromSelection) {
R
Rob Lourens 已提交
708
			let selectedText = this.getSearchTextFromEditor(allowUnselectedWord);
709
			if (selectedText) {
710 711 712 713
				if (this.searchWidget.searchInput.getRegex()) {
					selectedText = strings.escapeRegExpCharacters(selectedText);
				}

714
				this.searchWidget.searchInput.setValue(selectedText);
715
				updatedText = true;
716
			}
717
		}
R
Rob Lourens 已提交
718

R
Rob Lourens 已提交
719
		return updatedText;
720 721
	}

722 723
	public focusNextInputBox(): void {
		if (this.searchWidget.searchInputHasFocus()) {
724 725 726 727 728
			if (this.searchWidget.isReplaceShown()) {
				this.searchWidget.focus(true, true);
			} else {
				this.moveFocusFromSearchOrReplace();
			}
729 730 731 732
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
733
			this.moveFocusFromSearchOrReplace();
734 735 736 737
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
738 739 740 741 742 743
			this.inputPatternExcludes.focus();
			this.inputPatternExcludes.select();
			return;
		}

		if (this.inputPatternExcludes.inputHasFocus()) {
744 745 746 747 748
			this.selectTreeIfNotSelected();
			return;
		}
	}

749 750
	private moveFocusFromSearchOrReplace() {
		if (this.showsFileTypes()) {
751
			this.toggleQueryDetails(true, this.showsFileTypes());
752 753 754 755 756
		} else {
			this.selectTreeIfNotSelected();
		}
	}

757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
	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;
		}

772 773 774 775 776 777
		if (this.inputPatternExcludes.inputHasFocus()) {
			this.inputPatternIncludes.focus();
			this.inputPatternIncludes.select();
			return;
		}

S
Sandeep Somavarapu 已提交
778 779 780 781
		if (this.tree.isDOMFocused()) {
			this.moveFocusFromResults();
			return;
		}
782 783
	}

S
Sandeep Somavarapu 已提交
784
	private moveFocusFromResults(): void {
785
		if (this.showsFileTypes()) {
786
			this.toggleQueryDetails(true, true, false, true);
787 788 789
		} else {
			this.searchWidget.focus(true, true);
		}
E
Erich Gamma 已提交
790 791 792 793 794 795 796
	}

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

797
		if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
798
			this.isWide = true;
799
			dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
800
		} else {
801
			this.isWide = false;
802
			dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
803 804
		}

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

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

810 811 812 813
		const messagesSize = this.messagesElement.style.display === 'none' ?
			0 :
			dom.getTotalHeight(this.messagesElement);

814 815
		const searchResultContainerSize = this.size.height -
			messagesSize -
816
			dom.getTotalHeight(this.searchWidgetsContainerElement);
817

818
		this.resultsElement.style.height = searchResultContainerSize + 'px';
E
Erich Gamma 已提交
819 820 821 822

		this.tree.layout(searchResultContainerSize);
	}

823
	public layout(dimension: dom.Dimension): void {
E
Erich Gamma 已提交
824
		this.size = dimension;
B
Benjamin Pasero 已提交
825
		this.reLayout();
E
Erich Gamma 已提交
826 827 828 829 830 831
	}

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

S
Sandeep Somavarapu 已提交
832 833 834 835
	public isSearchSubmitted(): boolean {
		return this.searchSubmitted;
	}

836 837 838 839
	public isSearching(): boolean {
		return this.searching;
	}

S
Sandeep Somavarapu 已提交
840 841 842 843
	public hasSearchResults(): boolean {
		return !this.viewModel.searchResult.isEmpty();
	}

E
Erich Gamma 已提交
844
	public clearSearchResults(): void {
845
		this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
846
		this.showEmptyStage();
847
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
848
			this.showSearchWithoutFolderMessage();
849
		}
850
		this.searchWidget.clear();
S
Sandeep Somavarapu 已提交
851
		this.viewModel.cancelSearch();
E
Erich Gamma 已提交
852 853
	}

854
	public cancelSearch(): boolean {
S
Sandeep Somavarapu 已提交
855
		if (this.viewModel.cancelSearch()) {
856
			this.searchWidget.focus();
857 858 859 860 861
			return true;
		}
		return false;
	}

862
	private selectTreeIfNotSelected(): void {
E
Erich Gamma 已提交
863
		if (this.tree.getInput()) {
864
			this.tree.domFocus();
E
Erich Gamma 已提交
865 866 867 868 869 870 871
			let selection = this.tree.getSelection();
			if (selection.length === 0) {
				this.tree.focusNext();
			}
		}
	}

R
Rob Lourens 已提交
872
	private getSearchTextFromEditor(allowUnselectedWord: boolean): string {
B
Benjamin Pasero 已提交
873
		if (!this.editorService.activeEditor) {
E
Erich Gamma 已提交
874 875 876
			return null;
		}

877 878 879 880
		let activeTextEditorWidget = this.editorService.activeTextEditorWidget;
		if (isDiffEditor(activeTextEditorWidget)) {
			if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) {
				activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor();
881
			} else {
882
				activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
883 884 885
			}
		}

886
		if (!isCodeEditor(activeTextEditorWidget)) {
E
Erich Gamma 已提交
887 888 889
			return null;
		}

890
		const range = activeTextEditorWidget.getSelection();
R
Rob Lourens 已提交
891 892 893 894
		if (!range) {
			return null;
		}

R
Rob Lourens 已提交
895
		if (range.isEmpty() && !this.searchWidget.searchInput.getValue() && allowUnselectedWord) {
896
			const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition());
R
Rob Lourens 已提交
897 898 899 900 901 902
			if (wordAtPosition) {
				return wordAtPosition.word;
			}
		}

		if (!range.isEmpty() && range.startLineNumber === range.endLineNumber) {
903
			let searchText = activeTextEditorWidget.getModel().getLineContent(range.startLineNumber);
904 905
			searchText = searchText.substring(range.startColumn - 1, range.endColumn - 1);
			return searchText;
E
Erich Gamma 已提交
906
		}
907

E
Erich Gamma 已提交
908 909 910 911 912 913 914
		return null;
	}

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

S
Sandeep Somavarapu 已提交
915 916
	public toggleCaseSensitive(): void {
		this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive());
917
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
918 919 920 921
	}

	public toggleWholeWords(): void {
		this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords());
922
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
923 924 925 926
	}

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

930
	public toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean, reverse?: boolean): void {
E
Erich Gamma 已提交
931 932
		let cls = 'more';
		show = typeof show === 'undefined' ? !dom.hasClass(this.queryDetails, cls) : Boolean(show);
A
Amy Qiu 已提交
933
		this.viewletSettings['query.queryDetailsExpanded'] = show;
E
Erich Gamma 已提交
934 935 936
		skipLayout = Boolean(skipLayout);

		if (show) {
937
			this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true');
E
Erich Gamma 已提交
938
			dom.addClass(this.queryDetails, cls);
939
			if (moveFocus) {
940 941 942 943 944 945 946
				if (reverse) {
					this.inputPatternExcludes.focus();
					this.inputPatternExcludes.select();
				} else {
					this.inputPatternIncludes.focus();
					this.inputPatternIncludes.select();
				}
947
			}
E
Erich Gamma 已提交
948
		} else {
949
			this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'false');
E
Erich Gamma 已提交
950
			dom.removeClass(this.queryDetails, cls);
951
			if (moveFocus) {
952
				this.searchWidget.focus();
953
			}
E
Erich Gamma 已提交
954
		}
B
Benjamin Pasero 已提交
955

E
Erich Gamma 已提交
956 957 958 959 960
		if (!skipLayout && this.size) {
			this.layout(this.size);
		}
	}

961
	public searchInFolders(resources: URI[], pathToRelative: (from: string, to: string) => string): void {
962
		const folderPaths: string[] = [];
963
		const workspace = this.contextService.getWorkspace();
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985

		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) {
						const owningRootBasename = paths.basename(owningFolder.uri.fsPath);

						// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
						const isUniqueFolder = workspace.folders.filter(folder => paths.basename(folder.uri.fsPath) === owningRootBasename).length === 1;
						if (isUniqueFolder) {
							folderPath = `./${owningRootBasename}/${paths.normalize(pathToRelative(owningFolder.uri.fsPath, resource.fsPath))}`;
						} else {
							folderPath = resource.fsPath;
						}
986 987
					}
				}
988 989 990 991 992

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

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

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

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

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

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

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

E
Erich Gamma 已提交
1032
			if (strings.regExpLeadsToEndlessLoop(regExp)) {
1033
				return; // endless regex
E
Erich Gamma 已提交
1034 1035 1036
			}
		}

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

1045 1046
		const excludePattern = this.inputPatternExcludes.getValue();
		const includePattern = this.inputPatternIncludes.getValue();
E
Erich Gamma 已提交
1047

R
Rob Lourens 已提交
1048 1049 1050 1051 1052 1053 1054
		// 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
		const totalChars = content.isRegExp ? 10000 :
			this.isWide ? 250 :
				75;

1055
		const options: IQueryOptions = {
1056
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
1057
			maxResults: SearchView.MAX_TEXT_RESULTS,
1058 1059
			disregardIgnoreFiles: !useExcludesAndIgnoreFiles,
			disregardExcludeSettings: !useExcludesAndIgnoreFiles,
R
Rob Lourens 已提交
1060
			excludePattern,
1061 1062
			includePattern,
			previewOptions: {
1063
				leadingChars: 20,
1064
				maxLines: 1,
R
Rob Lourens 已提交
1065
				totalChars
1066
			}
E
Erich Gamma 已提交
1067
		};
S
Sandeep Somavarapu 已提交
1068
		const folderResources = this.contextService.getWorkspace().folders;
1069 1070 1071 1072 1073 1074 1075 1076

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

		let query: ISearchQuery;
		try {
1077
			query = this.queryBuilder.text(content, folderResources.map(folder => folder.uri), options);
1078 1079
		} catch (err) {
			onQueryValidationError(err);
1080
			return;
1081
		}
1082

1083
		this.validateQuery(query).then(() => {
1084
			this.onQueryTriggered(query, excludePatternText, includePatternText);
1085

1086 1087 1088
			if (!preserveFocus) {
				this.searchWidget.focus(false); // focus back to input field
			}
1089
		}, onQueryValidationError);
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
	}

	private validateQuery(query: ISearchQuery): TPromise<void> {
		// Validate folderQueries
		const folderQueriesExistP =
			query.folderQueries.map(fq => {
				return this.fileService.existsFile(fq.folder);
			});

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

			return undefined;
		});
E
Erich Gamma 已提交
1112 1113
	}

R
Rob Lourens 已提交
1114
	private onQueryTriggered(query: ISearchQuery, excludePatternText: string, includePatternText: string): void {
1115
		this.inputPatternExcludes.onSearchSubmit();
1116 1117
		this.inputPatternIncludes.onSearchSubmit();

S
Sandeep Somavarapu 已提交
1118 1119
		this.viewModel.cancelSearch();

1120 1121 1122 1123 1124 1125 1126
		// 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 已提交
1127

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

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

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

1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
			// 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;
1159
			});
1160

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

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

S
Sandeep Somavarapu 已提交
1165 1166
			this.searchSubmitted = true;
			this.updateActions();
E
Erich Gamma 已提交
1167

1168
			if (completed && completed.limitHit) {
1169
				this.searchWidget.searchInput.showMessage({
E
Erich Gamma 已提交
1170 1171 1172 1173 1174 1175
					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) {
1176 1177
				let hasExcludes = !!excludePatternText;
				let hasIncludes = !!includePatternText;
E
Erich Gamma 已提交
1178 1179
				let message: string;

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

1192 1193 1194
				// Indicate as status to ARIA
				aria.status(message);

E
Erich Gamma 已提交
1195
				this.tree.onHidden();
1196
				dom.hide(this.resultsElement);
1197 1198

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

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

1212
						this.inputPatternExcludes.setValue('');
E
Erich Gamma 已提交
1213 1214
						this.inputPatternIncludes.setValue('');

1215
						this.onQueryChanged();
1216
					}));
E
Erich Gamma 已提交
1217
				} else {
1218
					const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings")));
1219
					this.messageDisposables.push(dom.addDisposableListener(openSettingsLink, dom.EventType.CLICK, (e: MouseEvent) => {
E
Erich Gamma 已提交
1220 1221
						dom.EventHelper.stop(e, false);

1222 1223 1224 1225
						const options: ISettingsEditorOptions = { query: '.exclude' };
						this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ?
							this.preferencesService.openWorkspaceSettings(undefined, options) :
							this.preferencesService.openGlobalSettings(undefined, options);
1226
					}));
E
Erich Gamma 已提交
1227
				}
1228 1229

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

1232
					const learnMoreLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.learnMore', "Learn More")));
1233
					this.messageDisposables.push(dom.addDisposableListener(learnMoreLink, dom.EventType.CLICK, (e: MouseEvent) => {
1234 1235 1236
						dom.EventHelper.stop(e, false);

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

1240
				if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
1241
					this.showSearchWithoutFolderMessage();
1242
				}
E
Erich Gamma 已提交
1243
			} else {
R
Rob Lourens 已提交
1244
				this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights
1245

1246
				// Indicate final search result count for ARIA
1247
				aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount()));
E
Erich Gamma 已提交
1248 1249 1250 1251
			}
		};

		let onError = (e: any) => {
1252 1253 1254
			if (errors.isPromiseCanceledError(e)) {
				onComplete(null);
			} else {
1255 1256
				this.searching = false;
				this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1257
				progressRunner.done();
1258
				this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR });
1259
				this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
1260 1261 1262
			}
		};

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

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

1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306
			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 已提交
1307
			// Search result tree update
1308 1309 1310
			const fileCount = this.viewModel.searchResult.fileCount();
			if (visibleMatches !== fileCount) {
				visibleMatches = fileCount;
1311
				this.tree.refresh();
1312

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

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

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

1325
	private updateSearchResultCount(): void {
1326
		const fileCount = this.viewModel.searchResult.fileCount();
1327 1328
		this.hasSearchResultsKey.set(fileCount > 0);

1329
		const msgWasHidden = this.messagesElement.style.display === 'none';
1330
		if (fileCount > 0) {
1331
			const messageEl = this.clearMessage();
1332
			dom.append(messageEl, $('p', undefined, this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount)));
1333 1334 1335
			if (msgWasHidden) {
				this.reLayout();
			}
1336
		} else if (!msgWasHidden) {
1337
			dom.hide(this.messagesElement);
1338 1339 1340
		}
	}

1341 1342 1343 1344 1345 1346 1347
	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);
1348
		} else {
1349
			return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount);
1350 1351 1352
		}
	}

1353 1354 1355 1356
	private showSearchWithoutFolderMessage(): void {
		this.searchWithoutFolderMessageElement = this.clearMessage();

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

		const openFolderLink = dom.append(textEl,
1360
			$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
1361 1362 1363 1364 1365 1366

		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);
1367
			this.actionRunner.run(action).then(() => {
1368 1369 1370 1371
				action.dispose();
			}, err => {
				action.dispose();
				errors.onUnexpectedError(err);
1372
			});
1373
		}));
1374 1375
	}

E
Erich Gamma 已提交
1376
	private showEmptyStage(): void {
1377

E
Erich Gamma 已提交
1378
		// disable 'result'-actions
S
Sandeep Somavarapu 已提交
1379 1380
		this.searchSubmitted = false;
		this.updateActions();
E
Erich Gamma 已提交
1381 1382

		// clean up ui
S
Sandeep Somavarapu 已提交
1383
		// this.replaceService.disposeAllReplacePreviews();
1384
		dom.hide(this.messagesElement);
1385
		dom.show(this.resultsElement);
E
Erich Gamma 已提交
1386
		this.tree.onVisible();
S
Sandeep Somavarapu 已提交
1387
		this.currentSelectedFileMatch = null;
E
Erich Gamma 已提交
1388 1389
	}

S
Sandeep Somavarapu 已提交
1390
	private onFocus(lineMatch: any, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
E
Erich Gamma 已提交
1391
		if (!(lineMatch instanceof Match)) {
1392
			this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
A
Alex Dima 已提交
1393
			return TPromise.as(true);
E
Erich Gamma 已提交
1394 1395
		}

R
Rob Lourens 已提交
1396 1397 1398
		return (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1399 1400 1401
	}

	public open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
R
Rob Lourens 已提交
1402 1403
		const selection = this.getSelectionFrom(element);
		const resource = element instanceof Match ? element.parent().resource() : (<FileMatch>element).resource();
E
Erich Gamma 已提交
1404
		return this.editorService.openEditor({
1405
			resource: resource,
E
Erich Gamma 已提交
1406
			options: {
1407 1408
				preserveFocus,
				pinned,
S
Sandeep Somavarapu 已提交
1409
				selection,
1410
				revealIfVisible: true
E
Erich Gamma 已提交
1411
			}
B
Benjamin Pasero 已提交
1412
		}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => {
S
Sandeep Somavarapu 已提交
1413
			if (editor && element instanceof Match && preserveFocus) {
1414 1415 1416 1417
				this.viewModel.searchResult.rangeHighlightDecorations.highlightRange(
					(<ICodeEditor>editor.getControl()).getModel(),
					element.range()
				);
S
Sandeep Somavarapu 已提交
1418
			} else {
1419
				this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
S
Sandeep Somavarapu 已提交
1420
			}
R
Rob Lourens 已提交
1421 1422

			return this.editorGroupsService.activateGroup(editor.group);
S
Sandeep Somavarapu 已提交
1423
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1424 1425
	}

1426
	private getSelectionFrom(element: FileMatchOrMatch): any {
J
Johannes Rieken 已提交
1427
		let match: Match = null;
1428
		if (element instanceof Match) {
J
Johannes Rieken 已提交
1429
			match = element;
1430 1431
		}
		if (element instanceof FileMatch && element.count() > 0) {
J
Johannes Rieken 已提交
1432
			match = element.matches()[element.matches().length - 1];
1433 1434
		}
		if (match) {
J
Johannes Rieken 已提交
1435
			let range = match.range();
S
Sandeep Somavarapu 已提交
1436
			if (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) {
J
Johannes Rieken 已提交
1437
				let replaceString = match.replaceString;
1438 1439
				return {
					startLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1440
					startColumn: range.startColumn,
1441
					endLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1442
					endColumn: range.startColumn + replaceString.length
1443 1444 1445 1446 1447 1448 1449
				};
			}
			return range;
		}
		return void 0;
	}

B
Benjamin Pasero 已提交
1450
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1451 1452 1453 1454
		if (!this.viewModel) {
			return;
		}

1455
		// remove search results from this resource as it got disposed
B
Benjamin Pasero 已提交
1456
		if (!this.untitledEditorService.isDirty(resource)) {
1457 1458
			let matches = this.viewModel.searchResult.matches();
			for (let i = 0, len = matches.length; i < len; i++) {
B
Benjamin Pasero 已提交
1459
				if (resource.toString() === matches[i].resource().toString()) {
1460 1461
					this.viewModel.searchResult.remove(matches[i]);
				}
E
Erich Gamma 已提交
1462 1463 1464 1465 1466 1467 1468 1469 1470
			}
		}
	}

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

1471
		let matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1472 1473 1474

		for (let i = 0, len = matches.length; i < len; i++) {
			if (e.contains(matches[i].resource(), FileChangeType.DELETED)) {
1475
				this.viewModel.searchResult.remove(matches[i]);
E
Erich Gamma 已提交
1476 1477 1478 1479 1480
			}
		}
	}

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

1484
	private changeActionAtPosition(index: number, newAction: ClearSearchResultsAction | CancelSearchAction | RefreshAction | CollapseDeepestExpandedLevelAction): void {
1485 1486 1487 1488
		this.actions.splice(index, 1, newAction);
		this.updateTitleArea();
	}

1489
	private clearHistory(): void {
1490 1491 1492 1493 1494
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

A
Amy Qiu 已提交
1495
	public shutdown(): void {
A
Amy Qiu 已提交
1496 1497 1498 1499
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1500
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1501
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1502
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1503 1504 1505 1506 1507 1508

		// store memento
		this.viewletSettings['query.contentPattern'] = contentPattern;
		this.viewletSettings['query.regex'] = isRegex;
		this.viewletSettings['query.wholeWords'] = isWholeWords;
		this.viewletSettings['query.caseSensitive'] = isCaseSensitive;
1509
		this.viewletSettings['query.folderExclusions'] = patternExcludes;
A
Amy Qiu 已提交
1510
		this.viewletSettings['query.folderIncludes'] = patternIncludes;
1511
		this.viewletSettings['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles;
A
Amy Qiu 已提交
1512

1513 1514 1515 1516 1517
		const searchHistory = this.searchWidget.getSearchHistory();
		const replaceHistory = this.searchWidget.getReplaceHistory();
		const patternExcludesHistory = this.inputPatternExcludes.getHistory();
		const patternIncludesHistory = this.inputPatternIncludes.getHistory();

1518
		this.searchHistoryService.save({
1519 1520 1521 1522
			search: searchHistory,
			replace: replaceHistory,
			exclude: patternExcludesHistory,
			include: patternIncludesHistory
1523 1524
		});

A
Amy Qiu 已提交
1525 1526 1527
		super.shutdown();
	}

E
Erich Gamma 已提交
1528 1529 1530 1531 1532
	public dispose(): void {
		this.isDisposed = true;

		super.dispose();
	}
1533 1534 1535
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
B
Benjamin Pasero 已提交
1536
	const matchHighlightColor = theme.getColor(editorFindMatchHighlight);
1537
	if (matchHighlightColor) {
I
isidor 已提交
1538
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`);
B
Benjamin Pasero 已提交
1539 1540 1541 1542
	}

	const diffInsertedColor = theme.getColor(diffInserted);
	if (diffInsertedColor) {
I
isidor 已提交
1543
		collector.addRule(`.monaco-workbench .search-view .replaceMatch { background-color: ${diffInsertedColor}; }`);
B
Benjamin Pasero 已提交
1544 1545 1546 1547
	}

	const diffRemovedColor = theme.getColor(diffRemoved);
	if (diffRemovedColor) {
I
isidor 已提交
1548
		collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { background-color: ${diffRemovedColor}; }`);
B
Benjamin Pasero 已提交
1549 1550 1551 1552
	}

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

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

1561 1562
	const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
	if (findMatchHighlightBorder) {
1563
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findMatchHighlightBorder}; }`);
1564
	}
1565 1566 1567 1568 1569

	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} }`);
	}
1570
});