searchView.ts 59.0 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';
S
Sandeep Somavarapu 已提交
36
import { IPatternInfo, IQueryOptions, ISearchComplete, ISearchConfiguration, ISearchHistoryService, ISearchProgressItem, ISearchQuery, VIEW_ID } 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 52 53 54 55 56
import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/parts/search/browser/patternInputWidget';
import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction } from 'vs/workbench/parts/search/browser/searchActions';
import { SearchAccessibilityProvider, SearchDataSource, SearchFilter, SearchRenderer, SearchSorter, SearchTreeController } from 'vs/workbench/parts/search/browser/searchResultsView';
import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget';
import * as Constants from 'vs/workbench/parts/search/common/constants';
import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search';
import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, SearchModel } from 'vs/workbench/parts/search/common/searchModel';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
R
Rob Lourens 已提交
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
		}
	}

S
Sandeep Somavarapu 已提交
172
	public create(parent: HTMLElement): Promise<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 276
		this._register(this.onDidFocus(() => this.viewletFocused.set(true)));
		this._register(this.onDidBlur(() => this.viewletFocused.set(false)));

S
Sandeep Somavarapu 已提交
277
		return Promise.resolve(null);
278 279
	}

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

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

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

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

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

304
	private createSearchWidget(container: HTMLElement): void {
B
Benjamin Pasero 已提交
305 306 307 308
		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;
309
		const history = this.searchHistoryService.load();
B
Benjamin Pasero 已提交
310 311
		let searchHistory = history.search || this.viewletState['query.searchHistory'] || [];
		let replaceHistory = history.replace || this.viewletState['query.replaceHistory'] || [];
B
Benjamin Pasero 已提交
312
		let showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true;
E
Erich Gamma 已提交
313

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

499 500
	private clearMessage(): HTMLElement {
		this.searchWithoutFolderMessageElement = void 0;
501

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

507
		return dom.append(this.messagesElement, $('.message'));
508 509
	}

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

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

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

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

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

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

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

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

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

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

618
		let prev = navigator.previous();
619 620

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

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

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

S
Sandeep Somavarapu 已提交
656 657
	public setVisible(visible: boolean): Promise<void> {
		let promise: Promise<void>;
658
		this.viewletVisible.set(visible);
E
Erich Gamma 已提交
659
		if (visible) {
660 661 662 663 664 665
			if (this.changedWhileHidden) {
				// Render if results changed while viewlet was hidden - #37818
				this.refreshAndUpdateCount();
				this.changedWhileHidden = false;
			}

E
Erich Gamma 已提交
666 667 668 669 670 671 672 673 674
			promise = super.setVisible(visible);
			this.tree.onVisible();
		} else {
			this.tree.onHidden();
			promise = super.setVisible(visible);
		}

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

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

		return promise;
	}

Y
Yogesh 已提交
689 690 691 692
	public moveFocusToResults(): void {
		this.tree.domFocus();
	}

E
Erich Gamma 已提交
693 694 695
	public focus(): void {
		super.focus();

R
Rob Lourens 已提交
696 697 698 699 700
		const updatedText = this.updateTextFromSelection();
		this.searchWidget.focus(undefined, undefined, updatedText);
	}

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

710
				this.searchWidget.searchInput.setValue(selectedText);
711
				updatedText = true;
712
			}
713
		}
R
Rob Lourens 已提交
714

R
Rob Lourens 已提交
715
		return updatedText;
716 717
	}

718 719
	public focusNextInputBox(): void {
		if (this.searchWidget.searchInputHasFocus()) {
720 721 722 723 724
			if (this.searchWidget.isReplaceShown()) {
				this.searchWidget.focus(true, true);
			} else {
				this.moveFocusFromSearchOrReplace();
			}
725 726 727 728
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
729
			this.moveFocusFromSearchOrReplace();
730 731 732 733
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
734 735 736 737 738 739
			this.inputPatternExcludes.focus();
			this.inputPatternExcludes.select();
			return;
		}

		if (this.inputPatternExcludes.inputHasFocus()) {
740 741 742 743 744
			this.selectTreeIfNotSelected();
			return;
		}
	}

745 746
	private moveFocusFromSearchOrReplace() {
		if (this.showsFileTypes()) {
747
			this.toggleQueryDetails(true, this.showsFileTypes());
748 749 750 751 752
		} else {
			this.selectTreeIfNotSelected();
		}
	}

753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
	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;
		}

768 769 770 771 772 773
		if (this.inputPatternExcludes.inputHasFocus()) {
			this.inputPatternIncludes.focus();
			this.inputPatternIncludes.select();
			return;
		}

S
Sandeep Somavarapu 已提交
774 775 776 777
		if (this.tree.isDOMFocused()) {
			this.moveFocusFromResults();
			return;
		}
778 779
	}

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

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

793
		if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
794
			dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
795
		} else {
796
			dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
797 798
		}

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

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

804 805 806 807
		const messagesSize = this.messagesElement.style.display === 'none' ?
			0 :
			dom.getTotalHeight(this.messagesElement);

808 809
		const searchResultContainerSize = this.size.height -
			messagesSize -
810
			dom.getTotalHeight(this.searchWidgetsContainerElement);
811

812
		this.resultsElement.style.height = searchResultContainerSize + 'px';
E
Erich Gamma 已提交
813 814 815 816

		this.tree.layout(searchResultContainerSize);
	}

817
	public layout(dimension: dom.Dimension): void {
E
Erich Gamma 已提交
818
		this.size = dimension;
B
Benjamin Pasero 已提交
819
		this.reLayout();
E
Erich Gamma 已提交
820 821 822 823 824 825
	}

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

S
Sandeep Somavarapu 已提交
826 827 828 829
	public isSearchSubmitted(): boolean {
		return this.searchSubmitted;
	}

830 831 832 833
	public isSearching(): boolean {
		return this.searching;
	}

S
Sandeep Somavarapu 已提交
834 835 836 837
	public hasSearchResults(): boolean {
		return !this.viewModel.searchResult.isEmpty();
	}

E
Erich Gamma 已提交
838
	public clearSearchResults(): void {
839
		this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
840
		this.showEmptyStage();
841
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
842
			this.showSearchWithoutFolderMessage();
843
		}
844
		this.searchWidget.clear();
S
Sandeep Somavarapu 已提交
845
		this.viewModel.cancelSearch();
E
Erich Gamma 已提交
846 847
	}

848
	public cancelSearch(): boolean {
S
Sandeep Somavarapu 已提交
849
		if (this.viewModel.cancelSearch()) {
850
			this.searchWidget.focus();
851 852 853 854 855
			return true;
		}
		return false;
	}

856
	private selectTreeIfNotSelected(): void {
E
Erich Gamma 已提交
857
		if (this.tree.getInput()) {
858
			this.tree.domFocus();
E
Erich Gamma 已提交
859 860 861 862 863 864 865
			let selection = this.tree.getSelection();
			if (selection.length === 0) {
				this.tree.focusNext();
			}
		}
	}

R
Rob Lourens 已提交
866
	private getSearchTextFromEditor(allowUnselectedWord: boolean): string {
B
Benjamin Pasero 已提交
867
		if (!this.editorService.activeEditor) {
E
Erich Gamma 已提交
868 869 870
			return null;
		}

R
Rob Lourens 已提交
871 872 873 874
		if (dom.isAncestor(document.activeElement, this.getContainer())) {
			return null;
		}

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

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

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

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

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

E
Erich Gamma 已提交
906 907 908 909 910 911 912
		return null;
	}

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

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

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

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

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

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

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

959
	public searchInFolders(resources: URI[], pathToRelative: (from: string, to: string) => string): void {
960
		const folderPaths: string[] = [];
961
		const workspace = this.contextService.getWorkspace();
962 963 964 965 966 967 968 969 970 971 972 973 974

		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) {
975
						const owningRootName = owningFolder.name;
976 977

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

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

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

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

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

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

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

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

E
Erich Gamma 已提交
1035
			if (strings.regExpLeadsToEndlessLoop(regExp)) {
1036
				return; // endless regex
E
Erich Gamma 已提交
1037 1038 1039
			}
		}

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

1048 1049
		const excludePattern = this.inputPatternExcludes.getValue();
		const includePattern = this.inputPatternIncludes.getValue();
E
Erich Gamma 已提交
1050

R
Rob Lourens 已提交
1051 1052 1053
		// 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 已提交
1054
		const charsPerLine = content.isRegExp ? 10000 :
1055
			250;
R
Rob Lourens 已提交
1056

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1397 1398
		const useReplacePreview = this.configurationService.getValue<ISearchConfiguration>().search.useReplacePreview;
		return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
R
Rob Lourens 已提交
1399 1400
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1401 1402 1403
	}

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

R
Rob Lourens 已提交
1424 1425 1426 1427 1428
			if (editor) {
				return this.editorGroupsService.activateGroup(editor.group);
			} else {
				return TPromise.wrap(null);
			}
S
Sandeep Somavarapu 已提交
1429
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1430 1431
	}

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

B
Benjamin Pasero 已提交
1456
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1457 1458 1459 1460
		if (!this.viewModel) {
			return;
		}

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

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

1477
		let matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1478 1479 1480

		for (let i = 0, len = matches.length; i < len; i++) {
			if (e.contains(matches[i].resource(), FileChangeType.DELETED)) {
1481
				this.viewModel.searchResult.remove(matches[i]);
E
Erich Gamma 已提交
1482 1483 1484 1485 1486
			}
		}
	}

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

1490
	private changeActionAtPosition(index: number, newAction: ClearSearchResultsAction | CancelSearchAction | RefreshAction | CollapseDeepestExpandedLevelAction): void {
1491 1492 1493 1494
		this.actions.splice(index, 1, newAction);
		this.updateTitleArea();
	}

1495
	private clearHistory(): void {
1496 1497 1498 1499 1500
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

B
Benjamin Pasero 已提交
1501
	protected saveState(): void {
A
Amy Qiu 已提交
1502 1503 1504 1505
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1506
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1507
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1508
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1509

B
Benjamin Pasero 已提交
1510 1511 1512 1513 1514 1515 1516
		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 已提交
1517

B
Benjamin Pasero 已提交
1518 1519 1520
		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		this.viewletState['view.showReplace'] = isReplaceShown;

1521 1522 1523 1524 1525
		const searchHistory = this.searchWidget.getSearchHistory();
		const replaceHistory = this.searchWidget.getReplaceHistory();
		const patternExcludesHistory = this.inputPatternExcludes.getHistory();
		const patternIncludesHistory = this.inputPatternIncludes.getHistory();

1526
		this.searchHistoryService.save({
1527 1528 1529 1530
			search: searchHistory,
			replace: replaceHistory,
			exclude: patternExcludesHistory,
			include: patternIncludesHistory
1531
		});
B
Benjamin Pasero 已提交
1532 1533

		super.saveState();
A
Amy Qiu 已提交
1534 1535
	}

E
Erich Gamma 已提交
1536 1537 1538 1539 1540
	public dispose(): void {
		this.isDisposed = true;

		super.dispose();
	}
1541 1542 1543
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
B
Benjamin Pasero 已提交
1544
	const matchHighlightColor = theme.getColor(editorFindMatchHighlight);
1545
	if (matchHighlightColor) {
I
isidor 已提交
1546
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`);
B
Benjamin Pasero 已提交
1547 1548 1549 1550
	}

	const diffInsertedColor = theme.getColor(diffInserted);
	if (diffInsertedColor) {
I
isidor 已提交
1551
		collector.addRule(`.monaco-workbench .search-view .replaceMatch { background-color: ${diffInsertedColor}; }`);
B
Benjamin Pasero 已提交
1552 1553 1554 1555
	}

	const diffRemovedColor = theme.getColor(diffRemoved);
	if (diffRemovedColor) {
I
isidor 已提交
1556
		collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { background-color: ${diffRemovedColor}; }`);
B
Benjamin Pasero 已提交
1557 1558 1559 1560
	}

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

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

1569 1570
	const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
	if (findMatchHighlightBorder) {
1571
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findMatchHighlightBorder}; }`);
1572
	}
1573 1574 1575 1576 1577

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