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

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

62 63
const $ = dom.$;

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

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

68 69 70
	private static readonly WIDE_CLASS_NAME = 'wide';
	private static readonly WIDE_VIEW_SIZE = 600;

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

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

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

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

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

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

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

112
	private searchWithoutFolderMessageElement: HTMLElement;
113

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

233
		this.inputPatternIncludes.setValue(patternIncludes);
234

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

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

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

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

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

260
		this.createSearchResultsView(parent);
261

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
370
	private onSearchResultsChanged(event?: IChangeEvent): TPromise<any> {
371 372 373 374 375 376 377 378 379
		if (this.isVisible()) {
			return this.refreshAndUpdateCount(event);
		} else {
			this.changedWhileHidden = true;
			return TPromise.wrap(null);
		}
	}

	private refreshAndUpdateCount(event?: IChangeEvent): TPromise<void> {
S
Sandeep Somavarapu 已提交
380
		return this.refreshTree(event).then(() => {
S
Sandeep Somavarapu 已提交
381
			this.searchWidget.setReplaceAllActionState(!this.viewModel.searchResult.isEmpty());
382
			this.updateSearchResultCount();
S
Sandeep Somavarapu 已提交
383
		});
384 385
	}

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

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

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

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

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

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

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
	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);
	}

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

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

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

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

527 528 529 530 531 532 533 534
		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);
535
				}
536 537 538 539 540 541 542
				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 已提交
543

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
		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 已提交
564 565
	}

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

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

R
Rob Lourens 已提交
575 576 577 578 579 580 581 582 583 584
		// 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();
585
		if (!next) {
R
Rob Lourens 已提交
586 587 588 589
			// 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();
590 591 592
		}

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

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

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

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

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

		// Expand and go past FileMatch nodes
619 620
		if (!(prev instanceof Match)) {
			prev = navigator.previous();
R
Rob Lourens 已提交
621 622 623 624 625 626 627 628 629 630 631 632 633 634
			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;
				}
			}

635 636 637 638 639 640
			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();
641 642 643 644
			}
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
	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;
		}

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

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

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

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

788
		if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
789
			dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
790
		} else {
791
			dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
792 793
		}

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

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

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

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

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

		this.tree.layout(searchResultContainerSize);
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		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) {
970
						const owningRootName = owningFolder.name;
971 972

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1111
	private onQueryTriggered(query: ITextQuery, excludePatternText: string, includePatternText: string): void {
1112
		this.inputPatternExcludes.onSearchSubmit();
1113 1114
		this.inputPatternIncludes.onSearchSubmit();

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

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

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

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

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

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

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

				return null;
1156
			});
1157

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1249 1250
		let total: number = 0;
		let worked: number = 0;
E
Erich Gamma 已提交
1251
		let visibleMatches = 0;
1252 1253 1254 1255 1256 1257 1258 1259 1260
		let onProgress = (p: ISearchProgressItem) => {
			// Progress
			if (p.total) {
				total = p.total;
			}
			if (p.worked) {
				worked = p.worked;
			}
		};
E
Erich Gamma 已提交
1261 1262

		// Handle UI updates in an interval to show frequent progress and results
1263
		let uiRefreshHandle: any = setInterval(() => {
1264
			if (!this.searching) {
E
Erich Gamma 已提交
1265 1266 1267 1268
				window.clearInterval(uiRefreshHandle);
				return;
			}

1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
			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 已提交
1293
			// Search result tree update
1294 1295 1296
			const fileCount = this.viewModel.searchResult.fileCount();
			if (visibleMatches !== fileCount) {
				visibleMatches = fileCount;
1297
				this.tree.refresh();
1298

1299
				this.updateSearchResultCount();
1300
			}
1301
			if (fileCount > 0) {
S
Sandeep Somavarapu 已提交
1302
				this.updateActions();
E
Erich Gamma 已提交
1303
			}
1304
		}, 100);
E
Erich Gamma 已提交
1305

S
Sandeep Somavarapu 已提交
1306
		this.searchWidget.setReplaceAllActionState(false);
1307

1308
		this.viewModel.search(query, onProgress).then(onComplete, onError);
E
Erich Gamma 已提交
1309 1310
	}

1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
	private addClickEvents = (element: HTMLElement, handler: (event: any) => void): void => {
		this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.CLICK, handler));
		this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
			let event = new StandardKeyboardEvent(e as KeyboardEvent);
			let eventHandled = true;

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

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

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

		const options: ISettingsEditorOptions = { query: '.exclude' };
		this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ?
			this.preferencesService.openWorkspaceSettings(undefined, options) :
			this.preferencesService.openGlobalSettings(undefined, options);
	}

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

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

1345
	private updateSearchResultCount(): void {
1346
		const fileCount = this.viewModel.searchResult.fileCount();
1347 1348
		this.hasSearchResultsKey.set(fileCount > 0);

1349
		const msgWasHidden = this.messagesElement.style.display === 'none';
1350
		if (fileCount > 0) {
1351
			const messageEl = this.clearMessage();
1352
			dom.append(messageEl, $('p', undefined, this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount)));
1353 1354 1355
			if (msgWasHidden) {
				this.reLayout();
			}
1356
		} else if (!msgWasHidden) {
1357
			dom.hide(this.messagesElement);
1358 1359 1360
		}
	}

1361 1362 1363 1364 1365 1366 1367
	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);
1368
		} else {
1369
			return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount);
1370 1371 1372
		}
	}

1373 1374 1375 1376
	private showSearchWithoutFolderMessage(): void {
		this.searchWithoutFolderMessageElement = this.clearMessage();

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

		const openFolderLink = dom.append(textEl,
1380
			$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
1381 1382 1383 1384 1385 1386

		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);
1387
			this.actionRunner.run(action).then(() => {
1388 1389 1390 1391
				action.dispose();
			}, err => {
				action.dispose();
				errors.onUnexpectedError(err);
1392
			});
1393
		}));
1394 1395
	}

E
Erich Gamma 已提交
1396
	private showEmptyStage(): void {
1397

E
Erich Gamma 已提交
1398
		// disable 'result'-actions
S
Sandeep Somavarapu 已提交
1399 1400
		this.searchSubmitted = false;
		this.updateActions();
E
Erich Gamma 已提交
1401 1402

		// clean up ui
S
Sandeep Somavarapu 已提交
1403
		// this.replaceService.disposeAllReplacePreviews();
1404
		dom.hide(this.messagesElement);
1405
		dom.show(this.resultsElement);
E
Erich Gamma 已提交
1406
		this.tree.onVisible();
S
Sandeep Somavarapu 已提交
1407
		this.currentSelectedFileMatch = null;
E
Erich Gamma 已提交
1408 1409
	}

S
Sandeep Somavarapu 已提交
1410
	private onFocus(lineMatch: any, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
E
Erich Gamma 已提交
1411
		if (!(lineMatch instanceof Match)) {
1412
			this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
A
Alex Dima 已提交
1413
			return TPromise.as(true);
E
Erich Gamma 已提交
1414 1415
		}

1416 1417
		const useReplacePreview = this.configurationService.getValue<ISearchConfiguration>().search.useReplacePreview;
		return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
R
Rob Lourens 已提交
1418 1419
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1420 1421 1422
	}

	public open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
R
Rob Lourens 已提交
1423 1424
		const selection = this.getSelectionFrom(element);
		const resource = element instanceof Match ? element.parent().resource() : (<FileMatch>element).resource();
E
Erich Gamma 已提交
1425
		return this.editorService.openEditor({
1426
			resource: resource,
E
Erich Gamma 已提交
1427
			options: {
1428 1429
				preserveFocus,
				pinned,
S
Sandeep Somavarapu 已提交
1430
				selection,
1431
				revealIfVisible: true
E
Erich Gamma 已提交
1432
			}
B
Benjamin Pasero 已提交
1433
		}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => {
S
Sandeep Somavarapu 已提交
1434
			if (editor && element instanceof Match && preserveFocus) {
1435 1436 1437 1438
				this.viewModel.searchResult.rangeHighlightDecorations.highlightRange(
					(<ICodeEditor>editor.getControl()).getModel(),
					element.range()
				);
S
Sandeep Somavarapu 已提交
1439
			} else {
1440
				this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
S
Sandeep Somavarapu 已提交
1441
			}
R
Rob Lourens 已提交
1442

R
Rob Lourens 已提交
1443 1444 1445 1446 1447
			if (editor) {
				return this.editorGroupsService.activateGroup(editor.group);
			} else {
				return TPromise.wrap(null);
			}
S
Sandeep Somavarapu 已提交
1448
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1449 1450
	}

1451
	private getSelectionFrom(element: FileMatchOrMatch): any {
1452
		let match: Match | null = null;
1453
		if (element instanceof Match) {
J
Johannes Rieken 已提交
1454
			match = element;
1455 1456
		}
		if (element instanceof FileMatch && element.count() > 0) {
J
Johannes Rieken 已提交
1457
			match = element.matches()[element.matches().length - 1];
1458 1459
		}
		if (match) {
J
Johannes Rieken 已提交
1460
			let range = match.range();
S
Sandeep Somavarapu 已提交
1461
			if (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) {
J
Johannes Rieken 已提交
1462
				let replaceString = match.replaceString;
1463 1464
				return {
					startLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1465
					startColumn: range.startColumn,
1466
					endLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1467
					endColumn: range.startColumn + replaceString.length
1468 1469 1470 1471 1472 1473 1474
				};
			}
			return range;
		}
		return void 0;
	}

B
Benjamin Pasero 已提交
1475
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1476 1477 1478 1479
		if (!this.viewModel) {
			return;
		}

1480
		// remove search results from this resource as it got disposed
B
Benjamin Pasero 已提交
1481
		if (!this.untitledEditorService.isDirty(resource)) {
1482 1483
			let matches = this.viewModel.searchResult.matches();
			for (let i = 0, len = matches.length; i < len; i++) {
B
Benjamin Pasero 已提交
1484
				if (resource.toString() === matches[i].resource().toString()) {
1485 1486
					this.viewModel.searchResult.remove(matches[i]);
				}
E
Erich Gamma 已提交
1487 1488 1489 1490 1491 1492 1493 1494 1495
			}
		}
	}

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

1496
		let matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1497 1498 1499

		for (let i = 0, len = matches.length; i < len; i++) {
			if (e.contains(matches[i].resource(), FileChangeType.DELETED)) {
1500
				this.viewModel.searchResult.remove(matches[i]);
E
Erich Gamma 已提交
1501 1502 1503 1504 1505
			}
		}
	}

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

1509
	private changeActionAtPosition(index: number, newAction: ClearSearchResultsAction | CancelSearchAction | RefreshAction | CollapseDeepestExpandedLevelAction): void {
1510 1511 1512 1513
		this.actions.splice(index, 1, newAction);
		this.updateTitleArea();
	}

1514
	private clearHistory(): void {
1515 1516 1517 1518 1519
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

B
Benjamin Pasero 已提交
1520
	protected saveState(): void {
A
Amy Qiu 已提交
1521 1522 1523 1524
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1525
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1526
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1527
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1528

B
Benjamin Pasero 已提交
1529 1530 1531 1532 1533 1534 1535
		this.viewletState['query.contentPattern'] = contentPattern;
		this.viewletState['query.regex'] = isRegex;
		this.viewletState['query.wholeWords'] = isWholeWords;
		this.viewletState['query.caseSensitive'] = isCaseSensitive;
		this.viewletState['query.folderExclusions'] = patternExcludes;
		this.viewletState['query.folderIncludes'] = patternIncludes;
		this.viewletState['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles;
A
Amy Qiu 已提交
1536

B
Benjamin Pasero 已提交
1537 1538 1539
		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		this.viewletState['view.showReplace'] = isReplaceShown;

1540 1541
		const history: ISearchHistoryValues = Object.create(null);

1542
		const searchHistory = this.searchWidget.getSearchHistory();
1543 1544 1545 1546
		if (searchHistory && searchHistory.length) {
			history.search = searchHistory;
		}

1547
		const replaceHistory = this.searchWidget.getReplaceHistory();
1548 1549 1550 1551
		if (replaceHistory && replaceHistory.length) {
			history.replace = replaceHistory;
		}

1552
		const patternExcludesHistory = this.inputPatternExcludes.getHistory();
1553 1554 1555 1556
		if (patternExcludesHistory && patternExcludesHistory.length) {
			history.exclude = patternExcludesHistory;
		}

1557
		const patternIncludesHistory = this.inputPatternIncludes.getHistory();
1558 1559 1560
		if (patternIncludesHistory && patternIncludesHistory.length) {
			history.include = patternIncludesHistory;
		}
1561

1562
		this.searchHistoryService.save(history);
B
Benjamin Pasero 已提交
1563 1564

		super.saveState();
A
Amy Qiu 已提交
1565 1566
	}

E
Erich Gamma 已提交
1567 1568 1569 1570 1571
	public dispose(): void {
		this.isDisposed = true;

		super.dispose();
	}
1572 1573 1574
}

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

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

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

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

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

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

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