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

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

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

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

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

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

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

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

93
	private actions: (RefreshAction | CollapseDeepestExpandedLevelAction | ClearSearchResultsAction | CancelSearchAction)[] = [];
94
	private tree: WorkbenchTree;
E
Erich Gamma 已提交
95 96
	private viewletSettings: any;
	private messages: Builder;
97 98
	private searchWidgetsContainer: Builder;
	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;
E
Erich Gamma 已提交
104 105
	private results: Builder;

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 113
	private searchWithoutFolderMessageBuilder: Builder;

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,
122 123 124 125 126
		@IStorageService private storageService: IStorageService,
		@IContextViewService private contextViewService: IContextViewService,
		@IInstantiationService private instantiationService: IInstantiationService,
		@IConfigurationService private configurationService: IConfigurationService,
		@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 133
		@IThemeService protected themeService: IThemeService,
		@ISearchHistoryService private searchHistoryService: ISearchHistoryService
E
Erich Gamma 已提交
134
	) {
I
isidor 已提交
135
		super(VIEW_ID, partService, telemetryService, themeService);
E
Erich Gamma 已提交
136

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

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

152
		this.toUnbind.push(this.fileService.onFileChanges(e => this.onFilesChanged(e)));
153
		this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e)));
154
		this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState()));
155
		this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
156

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

S
Sandeep Somavarapu 已提交
161
		this.delayedRefresh = new Delayer<void>(250);
E
Erich Gamma 已提交
162 163
	}

164 165 166 167 168 169
	private onDidChangeWorkbenchState(): void {
		if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.searchWithoutFolderMessageBuilder) {
			this.searchWithoutFolderMessageBuilder.hide();
		}
	}

170
	public create(parent: HTMLElement): TPromise<void> {
E
Erich Gamma 已提交
171 172
		super.create(parent);

S
Sandeep Somavarapu 已提交
173
		this.viewModel = this.searchWorkbenchService.searchModel;
E
Erich Gamma 已提交
174
		let builder: Builder;
175
		$(parent).div({
I
isidor 已提交
176
			'class': 'search-view'
E
Erich Gamma 已提交
177 178 179 180
		}, (div) => {
			builder = div;
		});

J
Johannes Rieken 已提交
181 182
		builder.div({ 'class': ['search-widgets-container'] }, (div) => {
			this.searchWidgetsContainer = div;
183 184 185
		});
		this.createSearchWidget(this.searchWidgetsContainer);

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

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
		// Transition history from 1.22 combined include+exclude, to split include/exclude histories
		const patternIncludesHistoryWithoutExcludes: string[] = [];
		const patternExcludesHistoryFromIncludes: string[] = [];
		patternIncludesHistory.forEach(historyEntry => {
			const includeExclude = this.queryBuilder.parseIncludeExcludePattern(historyEntry);
			if (includeExclude.includePattern) {
				patternIncludesHistoryWithoutExcludes.push(includeExclude.includePattern);
			}

			if (includeExclude.excludePattern) {
				patternExcludesHistoryFromIncludes.push(includeExclude.excludePattern);
			}
		});

		patternIncludesHistory = patternIncludesHistoryWithoutExcludes;
		patternExclusionsHistory.push(...patternExcludesHistoryFromIncludes);

		// Split combined include/exclude to split include/exclude boxes
		const includeExclude = this.queryBuilder.parseIncludeExcludePattern(patternIncludes);
		patternIncludes = includeExclude.includePattern || '';

		if (includeExclude.excludePattern) {
			if (patternExclusions) {
				patternExclusions += ', ' + includeExclude.excludePattern;
			} else {
				patternExclusions = includeExclude.excludePattern;
			}
		}

225
		this.queryDetails = this.searchWidgetsContainer.div({ 'class': ['query-details'] }, (builder) => {
S
Sandeep Somavarapu 已提交
226
			this.toggleQueryDetailsButton = builder.div({ 'class': 'more', 'tabindex': 0, 'role': 'button', 'title': nls.localize('moreSearch', "Toggle Search Details") })
227 228
				.on(dom.EventType.CLICK, (e) => {
					dom.EventHelper.stop(e);
229
					this.toggleQueryDetails();
230 231 232
				}).on(dom.EventType.KEY_UP, (e: KeyboardEvent) => {
					let event = new StandardKeyboardEvent(e);

A
Alexandru Dima 已提交
233
					if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
234
						dom.EventHelper.stop(e);
235
						this.toggleQueryDetails(false);
236
					}
S
Sandeep Somavarapu 已提交
237 238 239 240 241 242 243 244 245 246 247 248
				}).on(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
					let event = new StandardKeyboardEvent(e);

					if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
						if (this.searchWidget.isReplaceActive()) {
							this.searchWidget.focusReplaceAllAction();
						} else {
							this.searchWidget.focusRegexAction();
						}
						dom.EventHelper.stop(e);
					}
				}).getHTMLElement();
E
Erich Gamma 已提交
249 250

			//folder includes list
J
Joao Moreno 已提交
251
			builder.div({ 'class': 'file-types includes' }, (builder) => {
252
				let title = nls.localize('searchScope.includes', "files to include");
E
Erich Gamma 已提交
253 254
				builder.element('h4', { text: title });

255
				this.inputPatternIncludes = this.instantiationService.createInstance(PatternInputWidget, builder.getContainer(), this.contextViewService, {
256 257
					ariaLabel: nls.localize('label.includes', 'Search Include Patterns'),
					history: patternIncludesHistory,
E
Erich Gamma 已提交
258 259
				});

260
				this.inputPatternIncludes.setValue(patternIncludes);
E
Erich Gamma 已提交
261 262

				this.inputPatternIncludes
263
					.on(FindInput.OPTION_CHANGE, (e) => {
E
Erich Gamma 已提交
264 265
						this.onQueryChanged(false);
					});
266

267
				this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true, true));
268
				this.inputPatternIncludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget
269
				this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused);
E
Erich Gamma 已提交
270
			});
271 272

			//pattern exclusion list
J
Joao Moreno 已提交
273
			builder.div({ 'class': 'file-types excludes' }, (builder) => {
274 275 276
				let title = nls.localize('searchScope.excludes', "files to exclude");
				builder.element('h4', { text: title });

277
				this.inputPatternExcludes = this.instantiationService.createInstance(ExcludePatternInputWidget, builder.getContainer(), this.contextViewService, {
278 279
					ariaLabel: nls.localize('label.excludes', 'Search Exclude Patterns'),
					history: patternExclusionsHistory,
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
				});

				this.inputPatternExcludes.setValue(patternExclusions);
				this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles);

				this.inputPatternExcludes
					.on(FindInput.OPTION_CHANGE, (e) => {
						this.onQueryChanged(false);
					});

				this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true, true));
				this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true, true));
				this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget
				this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused);
			});
E
Erich Gamma 已提交
295 296
		}).getHTMLElement();

297
		this.messages = builder.div({ 'class': 'messages' }).hide().clone();
298
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
299 300
			this.searchWithoutFolderMessage(this.clearMessage());
		}
301 302 303

		this.createSearchResultsView(builder);

S
Sandeep Somavarapu 已提交
304 305 306 307 308
		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)
		];
309

310
		if (filePatterns !== '' || patternExclusions !== '' || patternIncludes !== '' || queryDetailsExpanded !== '' || !useExcludesAndIgnoreFiles) {
311
			this.toggleQueryDetails(true, true, true);
312 313
		}

S
Sandeep Somavarapu 已提交
314
		this.toUnbind.push(this.viewModel.searchResult.onChange((event) => this.onSearchResultsChanged(event)));
S
Sandeep Somavarapu 已提交
315

316 317 318
		return TPromise.as(null);
	}

J
Johannes Rieken 已提交
319
	public get searchAndReplaceWidget(): SearchWidget {
320 321 322
		return this.searchWidget;
	}

323 324 325 326
	public get searchIncludePattern(): PatternInputWidget {
		return this.inputPatternIncludes;
	}

327 328 329 330
	public get searchExcludePattern(): PatternInputWidget {
		return this.inputPatternExcludes;
	}

S
Sandeep Somavarapu 已提交
331 332 333 334 335 336
	private updateActions(): void {
		for (const action of this.actions) {
			action.update();
		}
	}

337 338 339 340 341
	private createSearchWidget(builder: Builder): void {
		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;
342 343 344
		const history = this.searchHistoryService.load();
		let searchHistory = history.search || [];
		let replaceHistory = history.replace || [];
E
Erich Gamma 已提交
345

346
		this.searchWidget = this.instantiationService.createInstance(SearchWidget, builder, <ISearchWidgetOptions>{
347 348 349
			value: contentPattern,
			isRegex: isRegex,
			isCaseSensitive: isCaseSensitive,
350
			isWholeWords: isWholeWords,
351
			searchHistory: searchHistory,
352
			replaceHistory: replaceHistory
353
		});
S
Sandeep Somavarapu 已提交
354

355
		if (this.storageService.getBoolean(SearchView.SHOW_REPLACE_STORAGE_KEY, StorageScope.WORKSPACE, true)) {
S
Sandeep Somavarapu 已提交
356 357 358
			this.searchWidget.toggleReplace(true);
		}

S
Sandeep Somavarapu 已提交
359
		this.toUnbind.push(this.searchWidget);
360

S
Sandeep Somavarapu 已提交
361 362 363
		this.toUnbind.push(this.searchWidget.onSearchSubmit((refresh) => this.onQueryChanged(refresh)));
		this.toUnbind.push(this.searchWidget.onSearchCancel(() => this.cancelSearch()));
		this.toUnbind.push(this.searchWidget.searchInput.onDidOptionChange((viaKeyboard) => this.onQueryChanged(true, viaKeyboard)));
364

S
Sandeep Somavarapu 已提交
365
		this.toUnbind.push(this.searchWidget.onReplaceToggled(() => this.onReplaceToggled()));
S
Sandeep Somavarapu 已提交
366
		this.toUnbind.push(this.searchWidget.onReplaceStateChange((state) => {
J
Johannes Rieken 已提交
367
			this.viewModel.replaceActive = state;
368
			this.tree.refresh();
S
Sandeep Somavarapu 已提交
369 370
		}));
		this.toUnbind.push(this.searchWidget.onReplaceValueChanged((value) => {
J
Johannes Rieken 已提交
371
			this.viewModel.replaceString = this.searchWidget.getReplaceValue();
S
Sandeep Somavarapu 已提交
372
			this.delayedRefresh.trigger(() => this.tree.refresh());
S
Sandeep Somavarapu 已提交
373
		}));
374

S
Sandeep Somavarapu 已提交
375 376 377 378
		this.toUnbind.push(this.searchWidget.onBlur(() => {
			this.toggleQueryDetailsButton.focus();
		}));

S
Sandeep Somavarapu 已提交
379
		this.toUnbind.push(this.searchWidget.onReplaceAll(() => this.replaceAll()));
380 381 382 383
		this.trackInputBox(this.searchWidget.searchInputFocusTracker);
		this.trackInputBox(this.searchWidget.replaceInputFocusTracker);
	}

384
	private trackInputBox(inputFocusTracker: dom.IFocusTracker, contextKey?: IContextKey<boolean>): void {
385
		this.toUnbind.push(inputFocusTracker.onDidFocus(() => {
386
			this.inputBoxFocused.set(true);
387 388 389
			if (contextKey) {
				contextKey.set(true);
			}
390
		}));
391
		this.toUnbind.push(inputFocusTracker.onDidBlur(() => {
392
			this.inputBoxFocused.set(this.searchWidget.searchInputHasFocus()
393
				|| this.searchWidget.replaceInputHasFocus()
394 395
				|| this.inputPatternIncludes.inputHasFocus()
				|| this.inputPatternExcludes.inputHasFocus());
396 397 398
			if (contextKey) {
				contextKey.set(false);
			}
399
		}));
S
Sandeep Somavarapu 已提交
400 401
	}

S
Sandeep Somavarapu 已提交
402 403
	private onReplaceToggled(): void {
		this.layout(this.size);
404 405 406

		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		if (!isReplaceShown) {
407
			this.storageService.store(SearchView.SHOW_REPLACE_STORAGE_KEY, false, StorageScope.WORKSPACE);
408
		} else {
409
			this.storageService.remove(SearchView.SHOW_REPLACE_STORAGE_KEY);
410
		}
S
Sandeep Somavarapu 已提交
411 412
	}

S
Sandeep Somavarapu 已提交
413
	private onSearchResultsChanged(event?: IChangeEvent): TPromise<any> {
414 415 416 417 418 419 420 421 422
		if (this.isVisible()) {
			return this.refreshAndUpdateCount(event);
		} else {
			this.changedWhileHidden = true;
			return TPromise.wrap(null);
		}
	}

	private refreshAndUpdateCount(event?: IChangeEvent): TPromise<void> {
S
Sandeep Somavarapu 已提交
423
		return this.refreshTree(event).then(() => {
S
Sandeep Somavarapu 已提交
424
			this.searchWidget.setReplaceAllActionState(!this.viewModel.searchResult.isEmpty());
425
			this.updateSearchResultCount();
S
Sandeep Somavarapu 已提交
426
		});
427 428
	}

S
Sandeep Somavarapu 已提交
429
	private refreshTree(event?: IChangeEvent): TPromise<any> {
430
		if (!event || event.added || event.removed) {
S
Sandeep Somavarapu 已提交
431
			return this.tree.refresh(this.viewModel.searchResult);
432 433
		} else {
			if (event.elements.length === 1) {
S
Sandeep Somavarapu 已提交
434
				return this.tree.refresh(event.elements[0]);
435
			} else {
S
Sandeep Somavarapu 已提交
436
				return this.tree.refresh(event.elements);
437 438 439 440
			}
		}
	}

441
	private replaceAll(): void {
442
		if (this.viewModel.searchResult.count() === 0) {
S
Sandeep Somavarapu 已提交
443 444 445
			return;
		}

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

J
Johannes Rieken 已提交
448 449 450
		let occurrences = this.viewModel.searchResult.count();
		let fileCount = this.viewModel.searchResult.fileCount();
		let replaceValue = this.searchWidget.getReplaceValue() || '';
451
		let afterReplaceAllMessage = this.buildAfterReplaceAllMessage(occurrences, fileCount, replaceValue);
452

B
Benjamin Pasero 已提交
453
		let confirmation: IConfirmation = {
454
			title: nls.localize('replaceAll.confirmation.title', "Replace All"),
455
			message: this.buildReplaceAllConfirmationMessage(occurrences, fileCount, replaceValue),
456
			primaryButton: nls.localize('replaceAll.confirm.button', "&&Replace"),
B
Benjamin Pasero 已提交
457
			type: 'question'
458 459
		};

460
		this.dialogService.confirm(confirmation).then(res => {
461
			if (res.confirmed) {
462 463 464 465 466 467 468 469
				this.searchWidget.setReplaceAllActionState(false);
				this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
					progressRunner.done();
					this.clearMessage()
						.p({ text: afterReplaceAllMessage });
				}, (error) => {
					progressRunner.done();
					errors.isPromiseCanceledError(error);
470
					this.notificationService.error(error);
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 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
	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);
	}

540
	private clearMessage(): Builder {
541 542
		this.searchWithoutFolderMessageBuilder = void 0;

543 544 545
		return this.messages.empty().show()
			.asContainer().div({ 'class': 'message' })
			.asContainer();
546 547 548
	}

	private createSearchResultsView(builder: Builder): void {
E
Erich Gamma 已提交
549 550
		builder.div({ 'class': 'results' }, (div) => {
			this.results = div;
551
			this.results.addClass('show-file-icons');
E
Erich Gamma 已提交
552

553 554 555
			let dataSource = this.instantiationService.createInstance(SearchDataSource);
			this.toUnbind.push(dataSource);

556
			let renderer = this.instantiationService.createInstance(SearchRenderer, this.getActionRunner(), this);
557 558
			this.toUnbind.push(renderer);

559
			let dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, (obj: any) => obj instanceof FileMatch ? obj.resource() : void 0);
E
Erich Gamma 已提交
560

561
			this.tree = this.instantiationService.createInstance(WorkbenchTree, div.getHTMLElement(), {
E
Erich Gamma 已提交
562 563 564 565
				dataSource: dataSource,
				renderer: renderer,
				sorter: new SearchSorter(),
				filter: new SearchFilter(),
R
Rob Lourens 已提交
566
				controller: this.instantiationService.createInstance(SearchTreeController),
B
Benjamin Pasero 已提交
567 568
				accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider),
				dnd
B
Benjamin Pasero 已提交
569
			}, {
570 571
					ariaLabel: nls.localize('treeAriaLabel', "Search Results"),
					showLoading: false
572
				});
B
Benjamin Pasero 已提交
573

574
			this.tree.setInput(this.viewModel.searchResult);
A
Alex Dima 已提交
575
			this.toUnbind.push(renderer);
E
Erich Gamma 已提交
576

B
Benjamin Pasero 已提交
577 578
			const searchResultsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true }));
			this._register(debounceEvent(searchResultsNavigator.openResource, (last, event) => event, 75, true)(options => {
S
Sandeep Somavarapu 已提交
579 580 581 582 583 584 585 586
				if (options.element instanceof Match) {
					let selectedMatch: Match = options.element;
					if (this.currentSelectedFileMatch) {
						this.currentSelectedFileMatch.setSelectedMatch(null);
					}
					this.currentSelectedFileMatch = selectedMatch.parent();
					this.currentSelectedFileMatch.setSelectedMatch(selectedMatch);
					if (!(options.payload && options.payload.preventEditorOpen)) {
587
						this.onFocus(selectedMatch, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned);
S
Sandeep Somavarapu 已提交
588
					}
589
				}
S
Sandeep Somavarapu 已提交
590
			}));
591

592 593 594 595 596
			let treeHasFocus = false;
			this.tree.onDidFocus(() => {
				treeHasFocus = true;
			});

597
			this.toUnbind.push(this.tree.onDidChangeFocus((e: IFocusEvent) => {
598 599 600
				if (treeHasFocus) {
					const focus = e.focus;
					this.firstMatchFocused.set(this.tree.getNavigator().first() === focus);
R
Rob Lourens 已提交
601
					this.fileMatchOrMatchFocused.set(!!focus);
602 603 604
					this.fileMatchFocused.set(focus instanceof FileMatch);
					this.folderMatchFocused.set(focus instanceof FolderMatch);
					this.matchFocused.set(focus instanceof Match);
605
					this.fileMatchOrFolderMatchFocus.set(focus instanceof FileMatch || focus instanceof FolderMatch);
606
				}
E
Erich Gamma 已提交
607
			}));
S
Sandeep Somavarapu 已提交
608

609
			this.toUnbind.push(this.tree.onDidBlur(e => {
610
				treeHasFocus = false;
611 612 613
				this.firstMatchFocused.reset();
				this.fileMatchOrMatchFocused.reset();
				this.fileMatchFocused.reset();
614
				this.folderMatchFocused.reset();
615
				this.matchFocused.reset();
616
				this.fileMatchOrFolderMatchFocus.reset();
S
Sandeep Somavarapu 已提交
617
			}));
E
Erich Gamma 已提交
618 619 620
		});
	}

R
Rob Lourens 已提交
621 622
	public selectCurrentMatch(): void {
		const focused = this.tree.getFocus();
623
		const eventPayload = { focusEditor: true };
R
Rob Lourens 已提交
624 625 626 627
		this.tree.setSelection([focused], eventPayload);
	}

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

R
Rob Lourens 已提交
630 631 632 633 634 635 636 637 638 639
		// 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();
640
		if (!next) {
R
Rob Lourens 已提交
641 642 643 644
			// 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();
645 646 647
		}

		// Expand and go past FileMatch nodes
648 649 650
		if (!(next instanceof Match)) {
			if (!this.tree.isExpanded(next)) {
				this.tree.expand(next);
651 652 653
			}

			// Select the FileMatch's first child
654
			next = navigator.next();
655 656 657
		}

		// Reveal the newly selected element
658 659 660 661 662 663 664
		if (next) {
			const eventPayload = { preventEditorOpen: true };
			this.tree.setFocus(next, eventPayload);
			this.tree.setSelection([next], eventPayload);
			this.tree.reveal(next);
			this.selectCurrentMatchEmitter.fire();
		}
665 666
	}

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

671
		let prev = navigator.previous();
672 673

		// Expand and go past FileMatch nodes
674 675
		if (!(prev instanceof Match)) {
			prev = navigator.previous();
R
Rob Lourens 已提交
676 677 678 679 680 681 682 683 684 685 686 687 688 689
			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;
				}
			}

690 691 692 693 694 695
			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();
696 697 698 699
			}
		}

		// Reveal the newly selected element
700
		if (prev) {
R
Rob Lourens 已提交
701
			const eventPayload = { preventEditorOpen: true };
702 703 704
			this.tree.setFocus(prev, eventPayload);
			this.tree.setSelection([prev], eventPayload);
			this.tree.reveal(prev);
R
Rob Lourens 已提交
705
			this.selectCurrentMatchEmitter.fire();
706
		}
707 708
	}

E
Erich Gamma 已提交
709 710
	public setVisible(visible: boolean): TPromise<void> {
		let promise: TPromise<void>;
711
		this.viewletVisible.set(visible);
E
Erich Gamma 已提交
712
		if (visible) {
713 714 715 716 717 718
			if (this.changedWhileHidden) {
				// Render if results changed while viewlet was hidden - #37818
				this.refreshAndUpdateCount();
				this.changedWhileHidden = false;
			}

E
Erich Gamma 已提交
719 720 721 722 723 724 725 726 727
			promise = super.setVisible(visible);
			this.tree.onVisible();
		} else {
			this.tree.onHidden();
			promise = super.setVisible(visible);
		}

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

731
		// Open focused element from results in case the editor area is otherwise empty
B
Benjamin Pasero 已提交
732
		if (visible && !this.editorService.activeEditor) {
E
Erich Gamma 已提交
733 734
			let focus = this.tree.getFocus();
			if (focus) {
S
Sandeep Somavarapu 已提交
735
				this.onFocus(focus, true);
E
Erich Gamma 已提交
736 737 738 739 740 741 742 743 744
			}
		}

		return promise;
	}

	public focus(): void {
		super.focus();

745
		let updatedText = false;
746 747
		const seedSearchStringFromSelection = this.configurationService.getValue<IEditorOptions>('editor').find.seedSearchStringFromSelection;
		if (seedSearchStringFromSelection) {
748
			let selectedText = this.getSearchTextFromEditor();
749
			if (selectedText) {
750 751 752 753
				if (this.searchWidget.searchInput.getRegex()) {
					selectedText = strings.escapeRegExpCharacters(selectedText);
				}

754
				this.searchWidget.searchInput.setValue(selectedText);
755
				updatedText = true;
756
			}
757
		}
R
Rob Lourens 已提交
758

759
		this.searchWidget.focus(undefined, undefined, updatedText);
760 761
	}

762 763
	public focusNextInputBox(): void {
		if (this.searchWidget.searchInputHasFocus()) {
764 765 766 767 768
			if (this.searchWidget.isReplaceShown()) {
				this.searchWidget.focus(true, true);
			} else {
				this.moveFocusFromSearchOrReplace();
			}
769 770 771 772
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
773
			this.moveFocusFromSearchOrReplace();
774 775 776 777
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
778 779 780 781 782 783
			this.inputPatternExcludes.focus();
			this.inputPatternExcludes.select();
			return;
		}

		if (this.inputPatternExcludes.inputHasFocus()) {
784 785 786 787 788
			this.selectTreeIfNotSelected();
			return;
		}
	}

789 790
	private moveFocusFromSearchOrReplace() {
		if (this.showsFileTypes()) {
791
			this.toggleQueryDetails(true, this.showsFileTypes());
792 793 794 795 796
		} else {
			this.selectTreeIfNotSelected();
		}
	}

797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
	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;
		}

812 813 814 815 816 817
		if (this.inputPatternExcludes.inputHasFocus()) {
			this.inputPatternIncludes.focus();
			this.inputPatternIncludes.select();
			return;
		}

S
Sandeep Somavarapu 已提交
818 819 820 821
		if (this.tree.isDOMFocused()) {
			this.moveFocusFromResults();
			return;
		}
822 823
	}

S
Sandeep Somavarapu 已提交
824
	private moveFocusFromResults(): void {
825
		if (this.showsFileTypes()) {
826
			this.toggleQueryDetails(true, true, false, true);
827 828 829
		} else {
			this.searchWidget.focus(true, true);
		}
E
Erich Gamma 已提交
830 831 832 833 834 835 836
	}

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

837
		if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
838
			dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
839
		} else {
840
			dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
841 842
		}

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

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

848 849 850
		const messagesSize = this.messages.isHidden() ? 0 : dom.getTotalHeight(this.messages.getHTMLElement());
		const searchResultContainerSize = this.size.height -
			messagesSize -
851 852
			dom.getTotalHeight(this.searchWidgetsContainer.getContainer());

E
Erich Gamma 已提交
853 854 855 856 857
		this.results.style({ height: searchResultContainerSize + 'px' });

		this.tree.layout(searchResultContainerSize);
	}

858
	public layout(dimension: dom.Dimension): void {
E
Erich Gamma 已提交
859
		this.size = dimension;
B
Benjamin Pasero 已提交
860
		this.reLayout();
E
Erich Gamma 已提交
861 862 863 864 865 866
	}

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

S
Sandeep Somavarapu 已提交
867 868 869 870
	public isSearchSubmitted(): boolean {
		return this.searchSubmitted;
	}

871 872 873 874
	public isSearching(): boolean {
		return this.searching;
	}

S
Sandeep Somavarapu 已提交
875 876 877 878
	public hasSearchResults(): boolean {
		return !this.viewModel.searchResult.isEmpty();
	}

E
Erich Gamma 已提交
879
	public clearSearchResults(): void {
880
		this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
881
		this.showEmptyStage();
882
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
883 884
			this.searchWithoutFolderMessage(this.clearMessage());
		}
885
		this.searchWidget.clear();
S
Sandeep Somavarapu 已提交
886
		this.viewModel.cancelSearch();
E
Erich Gamma 已提交
887 888
	}

889
	public cancelSearch(): boolean {
S
Sandeep Somavarapu 已提交
890
		if (this.viewModel.cancelSearch()) {
891
			this.searchWidget.focus();
892 893 894 895 896
			return true;
		}
		return false;
	}

897
	private selectTreeIfNotSelected(): void {
E
Erich Gamma 已提交
898
		if (this.tree.getInput()) {
899
			this.tree.domFocus();
E
Erich Gamma 已提交
900 901 902 903 904 905 906
			let selection = this.tree.getSelection();
			if (selection.length === 0) {
				this.tree.focusNext();
			}
		}
	}

907
	private getSearchTextFromEditor(): string {
B
Benjamin Pasero 已提交
908
		if (!this.editorService.activeEditor) {
E
Erich Gamma 已提交
909 910 911
			return null;
		}

912 913 914 915
		let activeTextEditorWidget = this.editorService.activeTextEditorWidget;
		if (isDiffEditor(activeTextEditorWidget)) {
			if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) {
				activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor();
916
			} else {
917
				activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
918 919 920
			}
		}

921
		if (!isCodeEditor(activeTextEditorWidget)) {
E
Erich Gamma 已提交
922 923 924
			return null;
		}

925
		const range = activeTextEditorWidget.getSelection();
R
Rob Lourens 已提交
926 927 928 929 930
		if (!range) {
			return null;
		}

		if (range.isEmpty() && !this.searchWidget.searchInput.getValue()) {
931
			const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition());
R
Rob Lourens 已提交
932 933 934 935 936 937
			if (wordAtPosition) {
				return wordAtPosition.word;
			}
		}

		if (!range.isEmpty() && range.startLineNumber === range.endLineNumber) {
938
			let searchText = activeTextEditorWidget.getModel().getLineContent(range.startLineNumber);
939 940
			searchText = searchText.substring(range.startColumn - 1, range.endColumn - 1);
			return searchText;
E
Erich Gamma 已提交
941
		}
942

E
Erich Gamma 已提交
943 944 945 946 947 948 949
		return null;
	}

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

S
Sandeep Somavarapu 已提交
950 951 952 953 954 955 956 957 958 959 960 961 962 963 964
	public toggleCaseSensitive(): void {
		this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive());
		this.onQueryChanged(true, true);
	}

	public toggleWholeWords(): void {
		this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords());
		this.onQueryChanged(true, true);
	}

	public toggleRegex(): void {
		this.searchWidget.searchInput.setRegex(!this.searchWidget.searchInput.getRegex());
		this.onQueryChanged(true, true);
	}

965
	public toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean, reverse?: boolean): void {
E
Erich Gamma 已提交
966 967
		let cls = 'more';
		show = typeof show === 'undefined' ? !dom.hasClass(this.queryDetails, cls) : Boolean(show);
A
Amy Qiu 已提交
968
		this.viewletSettings['query.queryDetailsExpanded'] = show;
E
Erich Gamma 已提交
969 970 971 972
		skipLayout = Boolean(skipLayout);

		if (show) {
			dom.addClass(this.queryDetails, cls);
973
			if (moveFocus) {
974 975 976 977 978 979 980
				if (reverse) {
					this.inputPatternExcludes.focus();
					this.inputPatternExcludes.select();
				} else {
					this.inputPatternIncludes.focus();
					this.inputPatternIncludes.select();
				}
981
			}
E
Erich Gamma 已提交
982 983
		} else {
			dom.removeClass(this.queryDetails, cls);
984
			if (moveFocus) {
985
				this.searchWidget.focus();
986
			}
E
Erich Gamma 已提交
987
		}
B
Benjamin Pasero 已提交
988

E
Erich Gamma 已提交
989 990 991 992 993
		if (!skipLayout && this.size) {
			this.layout(this.size);
		}
	}

994
	public searchInFolders(resources: URI[], pathToRelative: (from: string, to: string) => string): void {
995
		const folderPaths: string[] = [];
996
		const workspace = this.contextService.getWorkspace();
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018

		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;
						}
1019 1020
					}
				}
1021 1022 1023 1024 1025

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

1028
		if (!folderPaths.length || folderPaths.some(folderPath => folderPath === '.')) {
S
Sandeep Somavarapu 已提交
1029 1030 1031 1032 1033
			this.inputPatternIncludes.setValue('');
			this.searchWidget.focus();
			return;
		}

1034
		// Show 'files to include' box
E
Erich Gamma 已提交
1035
		if (!this.showsFileTypes()) {
1036
			this.toggleQueryDetails(true, true);
E
Erich Gamma 已提交
1037
		}
B
Benjamin Pasero 已提交
1038

1039
		this.inputPatternIncludes.setValue(folderPaths.join(', '));
B
Benjamin Pasero 已提交
1040
		this.searchWidget.focus(false);
E
Erich Gamma 已提交
1041 1042
	}

1043
	public onQueryChanged(rerunQuery: boolean, preserveFocus?: boolean): void {
1044 1045 1046 1047
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1048 1049 1050
		const excludePatternText = this.inputPatternExcludes.getValue().trim();
		const includePatternText = this.inputPatternIncludes.getValue().trim();
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
E
Erich Gamma 已提交
1051 1052 1053 1054 1055 1056 1057 1058 1059

		if (!rerunQuery) {
			return;
		}

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

1060
		// Validate regex is OK
E
Erich Gamma 已提交
1061 1062 1063 1064 1065
		if (isRegex) {
			let regExp: RegExp;
			try {
				regExp = new RegExp(contentPattern);
			} catch (e) {
1066
				return; // malformed regex
E
Erich Gamma 已提交
1067
			}
1068

E
Erich Gamma 已提交
1069
			if (strings.regExpLeadsToEndlessLoop(regExp)) {
1070
				return; // endless regex
E
Erich Gamma 已提交
1071 1072 1073
			}
		}

1074
		const content: IPatternInfo = {
E
Erich Gamma 已提交
1075 1076 1077
			pattern: contentPattern,
			isRegExp: isRegex,
			isCaseSensitive: isCaseSensitive,
1078
			isWordMatch: isWholeWords,
1079 1080
			wordSeparators: this.configurationService.getValue<ISearchConfiguration>().editor.wordSeparators,
			isSmartCase: this.configurationService.getValue<ISearchConfiguration>().search.smartCase
E
Erich Gamma 已提交
1081 1082
		};

1083 1084
		const excludePattern = this.inputPatternExcludes.getValue();
		const includePattern = this.inputPatternIncludes.getValue();
E
Erich Gamma 已提交
1085

1086
		const options: IQueryOptions = {
1087
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
1088
			maxResults: SearchView.MAX_TEXT_RESULTS,
1089 1090
			disregardIgnoreFiles: !useExcludesAndIgnoreFiles,
			disregardExcludeSettings: !useExcludesAndIgnoreFiles,
R
Rob Lourens 已提交
1091 1092
			excludePattern,
			includePattern
E
Erich Gamma 已提交
1093
		};
S
Sandeep Somavarapu 已提交
1094
		const folderResources = this.contextService.getWorkspace().folders;
1095 1096 1097 1098 1099 1100 1101 1102

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

		let query: ISearchQuery;
		try {
1103
			query = this.queryBuilder.text(content, folderResources.map(folder => folder.uri), options);
1104 1105
		} catch (err) {
			onQueryValidationError(err);
1106
			return;
1107
		}
1108

1109
		this.validateQuery(query).then(() => {
1110
			this.onQueryTriggered(query, excludePatternText, includePatternText);
1111

1112 1113 1114
			if (!preserveFocus) {
				this.searchWidget.focus(false); // focus back to input field
			}
1115
		}, onQueryValidationError);
1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
	}

	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 已提交
1126 1127
			// If no folders exist, show an error message about the first one
			const existingFolderQueries = query.folderQueries.filter((folderQuery, i) => existResults[i]);
1128
			if (!query.folderQueries.length || existingFolderQueries.length) {
R
Rob Lourens 已提交
1129 1130 1131
				query.folderQueries = existingFolderQueries;
			} else {
				const nonExistantPath = query.folderQueries[0].folder.fsPath;
1132 1133 1134 1135 1136 1137
				const searchPathNotFoundError = nls.localize('searchPathNotFoundError', "Search path not found: {0}", nonExistantPath);
				return TPromise.wrapError(new Error(searchPathNotFoundError));
			}

			return undefined;
		});
E
Erich Gamma 已提交
1138 1139
	}

R
Rob Lourens 已提交
1140
	private onQueryTriggered(query: ISearchQuery, excludePatternText: string, includePatternText: string): void {
1141
		this.inputPatternExcludes.onSearchSubmit();
1142 1143
		this.inputPatternIncludes.onSearchSubmit();

S
Sandeep Somavarapu 已提交
1144 1145
		this.viewModel.cancelSearch();

1146 1147 1148 1149 1150 1151 1152
		// 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 已提交
1153

1154
		this.searchWidget.searchInput.clearMessage();
1155 1156 1157 1158 1159 1160
		this.searching = true;
		setTimeout(() => {
			if (this.searching) {
				this.changeActionAtPosition(0, this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL));
			}
		}, 2000);
E
Erich Gamma 已提交
1161 1162
		this.showEmptyStage();

1163
		let onComplete = (completed?: ISearchComplete) => {
1164 1165
			this.searching = false;
			this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1166 1167 1168 1169 1170 1171 1172 1173

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

1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
			// 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;
			}).done(null, errors.onUnexpectedError);

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

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

S
Sandeep Somavarapu 已提交
1191 1192
			this.searchSubmitted = true;
			this.updateActions();
E
Erich Gamma 已提交
1193

1194
			if (completed && completed.limitHit) {
1195
				this.searchWidget.searchInput.showMessage({
E
Erich Gamma 已提交
1196 1197 1198 1199 1200 1201
					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) {
1202 1203
				let hasExcludes = !!excludePatternText;
				let hasIncludes = !!includePatternText;
E
Erich Gamma 已提交
1204 1205
				let message: string;

1206 1207 1208
				if (!completed) {
					message = nls.localize('searchCanceled', "Search was canceled before any results could be found - ");
				} else if (hasIncludes && hasExcludes) {
R
Rob Lourens 已提交
1209
					message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText);
E
Erich Gamma 已提交
1210
				} else if (hasIncludes) {
R
Rob Lourens 已提交
1211
					message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText);
E
Erich Gamma 已提交
1212
				} else if (hasExcludes) {
R
Rob Lourens 已提交
1213
					message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText);
E
Erich Gamma 已提交
1214
				} else {
1215
					message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and ignore files - ");
E
Erich Gamma 已提交
1216 1217
				}

1218 1219 1220
				// Indicate as status to ARIA
				aria.status(message);

E
Erich Gamma 已提交
1221 1222
				this.tree.onHidden();
				this.results.hide();
1223 1224
				const div = this.clearMessage();
				const p = $(div).p({ text: message });
E
Erich Gamma 已提交
1225

1226
				if (!completed) {
1227
					$(p).a({
1228 1229 1230 1231 1232 1233 1234 1235
						'class': ['pointer', 'prominent'],
						text: nls.localize('rerunSearch.message', "Search again")
					}).on(dom.EventType.CLICK, (e: MouseEvent) => {
						dom.EventHelper.stop(e, false);

						this.onQueryChanged(true);
					});
				} else if (hasIncludes || hasExcludes) {
1236
					$(p).a({
E
Erich Gamma 已提交
1237
						'class': ['pointer', 'prominent'],
1238
						'tabindex': '0',
1239
						text: nls.localize('rerunSearchInAll.message', "Search again in all files")
E
Erich Gamma 已提交
1240 1241 1242
					}).on(dom.EventType.CLICK, (e: MouseEvent) => {
						dom.EventHelper.stop(e, false);

1243
						this.inputPatternExcludes.setValue('');
E
Erich Gamma 已提交
1244 1245 1246 1247 1248
						this.inputPatternIncludes.setValue('');

						this.onQueryChanged(true);
					});
				} else {
1249
					$(p).a({
E
Erich Gamma 已提交
1250
						'class': ['pointer', 'prominent'],
1251
						'tabindex': '0',
E
Erich Gamma 已提交
1252 1253 1254 1255
						text: nls.localize('openSettings.message', "Open Settings")
					}).on(dom.EventType.CLICK, (e: MouseEvent) => {
						dom.EventHelper.stop(e, false);

1256
						let editorPromise = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.preferencesService.openWorkspaceSettings() : this.preferencesService.openGlobalSettings();
1257 1258 1259 1260 1261
						editorPromise.done(editor => {
							if (editor instanceof PreferencesEditor) {
								editor.focusSearch('.exclude');
							}
						}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1262 1263
					});
				}
1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279

				if (completed) {
					$(p).span({
						text: ' - '
					});

					$(p).a({
						'class': ['pointer', 'prominent'],
						'tabindex': '0',
						text: nls.localize('openSettings.learnMore', "Learn More")
					}).on(dom.EventType.CLICK, (e: MouseEvent) => {
						dom.EventHelper.stop(e, false);

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

1281
				if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
1282 1283
					this.searchWithoutFolderMessage(div);
				}
E
Erich Gamma 已提交
1284
			} else {
1285
				this.viewModel.searchResult.toggleHighlights(true); // show highlights
1286

1287
				// Indicate final search result count for ARIA
1288
				aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount()));
E
Erich Gamma 已提交
1289 1290 1291 1292
			}
		};

		let onError = (e: any) => {
1293 1294 1295
			if (errors.isPromiseCanceledError(e)) {
				onComplete(null);
			} else {
1296 1297
				this.searching = false;
				this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1298
				progressRunner.done();
1299
				this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR });
1300
				this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
1301 1302 1303
			}
		};

1304 1305
		let total: number = 0;
		let worked: number = 0;
E
Erich Gamma 已提交
1306
		let visibleMatches = 0;
1307 1308 1309 1310 1311 1312 1313 1314 1315
		let onProgress = (p: ISearchProgressItem) => {
			// Progress
			if (p.total) {
				total = p.total;
			}
			if (p.worked) {
				worked = p.worked;
			}
		};
E
Erich Gamma 已提交
1316 1317 1318

		// Handle UI updates in an interval to show frequent progress and results
		let uiRefreshHandle = setInterval(() => {
1319
			if (!this.searching) {
E
Erich Gamma 已提交
1320 1321 1322 1323
				window.clearInterval(uiRefreshHandle);
				return;
			}

1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
			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 已提交
1348
			// Search result tree update
1349 1350 1351
			const fileCount = this.viewModel.searchResult.fileCount();
			if (visibleMatches !== fileCount) {
				visibleMatches = fileCount;
1352
				this.tree.refresh().done(null, errors.onUnexpectedError);
1353

1354
				this.updateSearchResultCount();
1355
			}
1356
			if (fileCount > 0) {
S
Sandeep Somavarapu 已提交
1357
				this.updateActions();
E
Erich Gamma 已提交
1358
			}
1359
		}, 100);
E
Erich Gamma 已提交
1360

S
Sandeep Somavarapu 已提交
1361
		this.searchWidget.setReplaceAllActionState(false);
1362 1363

		this.viewModel.search(query).done(onComplete, onError, onProgress);
E
Erich Gamma 已提交
1364 1365
	}

1366
	private updateSearchResultCount(): void {
1367
		const fileCount = this.viewModel.searchResult.fileCount();
1368 1369
		this.hasSearchResultsKey.set(fileCount > 0);

1370
		const msgWasHidden = this.messages.isHidden();
1371 1372
		if (fileCount > 0) {
			const div = this.clearMessage();
1373
			$(div).p({ text: this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount) });
1374 1375 1376
			if (msgWasHidden) {
				this.reLayout();
			}
1377 1378
		} else if (!msgWasHidden) {
			this.messages.hide();
1379 1380 1381
		}
	}

1382 1383 1384 1385 1386 1387 1388
	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);
1389
		} else {
1390
			return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount);
1391 1392 1393
		}
	}

1394
	private searchWithoutFolderMessage(div: Builder): void {
1395 1396 1397
		this.searchWithoutFolderMessageBuilder = $(div);

		this.searchWithoutFolderMessageBuilder.p({ text: nls.localize('searchWithoutFolder', "You have not yet opened a folder. Only open files are currently searched - ") })
1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415
			.asContainer().a({
				'class': ['pointer', 'prominent'],
				'tabindex': '0',
				text: nls.localize('openFolder', "Open Folder")
			}).on(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);
				this.actionRunner.run(action).done(() => {
					action.dispose();
				}, err => {
					action.dispose();
					errors.onUnexpectedError(err);
				});
			});
	}

E
Erich Gamma 已提交
1416
	private showEmptyStage(): void {
1417

E
Erich Gamma 已提交
1418
		// disable 'result'-actions
S
Sandeep Somavarapu 已提交
1419 1420
		this.searchSubmitted = false;
		this.updateActions();
E
Erich Gamma 已提交
1421 1422

		// clean up ui
S
Sandeep Somavarapu 已提交
1423
		// this.replaceService.disposeAllReplacePreviews();
E
Erich Gamma 已提交
1424 1425 1426
		this.messages.hide();
		this.results.show();
		this.tree.onVisible();
S
Sandeep Somavarapu 已提交
1427
		this.currentSelectedFileMatch = null;
E
Erich Gamma 已提交
1428 1429
	}

S
Sandeep Somavarapu 已提交
1430
	private onFocus(lineMatch: any, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
E
Erich Gamma 已提交
1431
		if (!(lineMatch instanceof Match)) {
1432
			this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
A
Alex Dima 已提交
1433
			return TPromise.as(true);
E
Erich Gamma 已提交
1434 1435
		}

R
Rob Lourens 已提交
1436 1437 1438
		return (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1439 1440 1441
	}

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

1464
	private getSelectionFrom(element: FileMatchOrMatch): any {
J
Johannes Rieken 已提交
1465
		let match: Match = null;
1466
		if (element instanceof Match) {
J
Johannes Rieken 已提交
1467
			match = element;
1468 1469
		}
		if (element instanceof FileMatch && element.count() > 0) {
J
Johannes Rieken 已提交
1470
			match = element.matches()[element.matches().length - 1];
1471 1472
		}
		if (match) {
J
Johannes Rieken 已提交
1473
			let range = match.range();
S
Sandeep Somavarapu 已提交
1474
			if (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) {
J
Johannes Rieken 已提交
1475
				let replaceString = match.replaceString;
1476 1477
				return {
					startLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1478
					startColumn: range.startColumn,
1479
					endLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1480
					endColumn: range.startColumn + replaceString.length
1481 1482 1483 1484 1485 1486 1487
				};
			}
			return range;
		}
		return void 0;
	}

B
Benjamin Pasero 已提交
1488
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1489 1490 1491 1492
		if (!this.viewModel) {
			return;
		}

1493
		// remove search results from this resource as it got disposed
B
Benjamin Pasero 已提交
1494
		if (!this.untitledEditorService.isDirty(resource)) {
1495 1496
			let matches = this.viewModel.searchResult.matches();
			for (let i = 0, len = matches.length; i < len; i++) {
B
Benjamin Pasero 已提交
1497
				if (resource.toString() === matches[i].resource().toString()) {
1498 1499
					this.viewModel.searchResult.remove(matches[i]);
				}
E
Erich Gamma 已提交
1500 1501 1502 1503 1504 1505 1506 1507 1508
			}
		}
	}

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

1509
		let matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1510 1511 1512

		for (let i = 0, len = matches.length; i < len; i++) {
			if (e.contains(matches[i].resource(), FileChangeType.DELETED)) {
1513
				this.viewModel.searchResult.remove(matches[i]);
E
Erich Gamma 已提交
1514 1515 1516 1517 1518
			}
		}
	}

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

1522
	private changeActionAtPosition(index: number, newAction: ClearSearchResultsAction | CancelSearchAction | RefreshAction | CollapseDeepestExpandedLevelAction): void {
1523 1524 1525 1526
		this.actions.splice(index, 1, newAction);
		this.updateTitleArea();
	}

1527
	private clearHistory(): void {
1528 1529 1530 1531 1532
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

A
Amy Qiu 已提交
1533
	public shutdown(): void {
A
Amy Qiu 已提交
1534 1535 1536 1537
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1538
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1539
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1540
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1541 1542 1543 1544 1545 1546

		// store memento
		this.viewletSettings['query.contentPattern'] = contentPattern;
		this.viewletSettings['query.regex'] = isRegex;
		this.viewletSettings['query.wholeWords'] = isWholeWords;
		this.viewletSettings['query.caseSensitive'] = isCaseSensitive;
1547
		this.viewletSettings['query.folderExclusions'] = patternExcludes;
A
Amy Qiu 已提交
1548
		this.viewletSettings['query.folderIncludes'] = patternIncludes;
1549
		this.viewletSettings['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles;
A
Amy Qiu 已提交
1550

1551 1552 1553 1554 1555 1556 1557
		this.searchHistoryService.save({
			search: this.searchWidget.getSearchHistory(),
			replace: this.searchWidget.getReplaceHistory(),
			exclude: this.inputPatternExcludes.getHistory(),
			include: this.inputPatternIncludes.getHistory()
		});

A
Amy Qiu 已提交
1558 1559 1560
		super.shutdown();
	}

E
Erich Gamma 已提交
1561 1562 1563 1564 1565 1566 1567
	public dispose(): void {
		this.isDisposed = true;

		if (this.tree) {
			this.tree.dispose();
		}

1568
		this.searchWidget.dispose();
1569
		this.inputPatternIncludes.dispose();
1570
		this.inputPatternExcludes.dispose();
1571

1572
		this.viewModel.dispose();
E
Erich Gamma 已提交
1573 1574 1575

		super.dispose();
	}
1576 1577 1578
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
B
Benjamin Pasero 已提交
1579
	const matchHighlightColor = theme.getColor(editorFindMatchHighlight);
1580
	if (matchHighlightColor) {
I
isidor 已提交
1581
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`);
B
Benjamin Pasero 已提交
1582 1583 1584 1585
	}

	const diffInsertedColor = theme.getColor(diffInserted);
	if (diffInsertedColor) {
I
isidor 已提交
1586
		collector.addRule(`.monaco-workbench .search-view .replaceMatch { background-color: ${diffInsertedColor}; }`);
B
Benjamin Pasero 已提交
1587 1588 1589 1590
	}

	const diffRemovedColor = theme.getColor(diffRemoved);
	if (diffRemovedColor) {
I
isidor 已提交
1591
		collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { background-color: ${diffRemovedColor}; }`);
B
Benjamin Pasero 已提交
1592 1593 1594 1595
	}

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

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

1604 1605
	const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
	if (findMatchHighlightBorder) {
1606
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findMatchHighlightBorder}; }`);
1607
	}
1608
});