searchView.ts 59.3 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';
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 45
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { Scope } from 'vs/workbench/common/memento';
46 47
import { IPanel } from 'vs/workbench/common/panel';
import { IViewlet } from 'vs/workbench/common/viewlet';
48 49 50 51 52 53 54 55 56 57
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 已提交
58
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
S
Sandeep Somavarapu 已提交
59
import { IPartService } from 'vs/workbench/services/part/common/partService';
60
import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
61
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
E
Erich Gamma 已提交
62

63 64
const $ = dom.$;

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

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

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

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

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

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

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

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

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

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

114
	private searchWithoutFolderMessageElement: HTMLElement;
115

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
174
	public create(parent: HTMLElement): Promise<void> {
E
Erich Gamma 已提交
175 176
		super.create(parent);

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

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

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

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

195 196 197
		// 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 已提交
198

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

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

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

224 225 226 227 228
		// 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));
229

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

235
		this.inputPatternIncludes.setValue(patternIncludes);
236

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

250 251
		this.inputPatternExcludes.setValue(patternExclusions);
		this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles);
252

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

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

262
		this.createSearchResultsView(parent);
263

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

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

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

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

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

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

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

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

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

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

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

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

324
		if (this.storageService.getBoolean(SearchView.SHOW_REPLACE_STORAGE_KEY, StorageScope.WORKSPACE, true)) {
S
Sandeep Somavarapu 已提交
325 326 327
			this.searchWidget.toggleReplace(true);
		}

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

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

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

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

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

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

		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		if (!isReplaceShown) {
374
			this.storageService.store(SearchView.SHOW_REPLACE_STORAGE_KEY, false, StorageScope.WORKSPACE);
375
		} else {
376
			this.storageService.remove(SearchView.SHOW_REPLACE_STORAGE_KEY, StorageScope.WORKSPACE);
377
		}
S
Sandeep Somavarapu 已提交
378 379
	}

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

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

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

408
	private replaceAll(): void {
409
		if (this.viewModel.searchResult.count() === 0) {
S
Sandeep Somavarapu 已提交
410 411 412
			return;
		}

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

J
Johannes Rieken 已提交
415 416 417
		let occurrences = this.viewModel.searchResult.count();
		let fileCount = this.viewModel.searchResult.fileCount();
		let replaceValue = this.searchWidget.getReplaceValue() || '';
418
		let afterReplaceAllMessage = this.buildAfterReplaceAllMessage(occurrences, fileCount, replaceValue);
419

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

427
		this.dialogService.confirm(confirmation).then(res => {
428
			if (res.confirmed) {
429 430 431
				this.searchWidget.setReplaceAllActionState(false);
				this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
					progressRunner.done();
432
					const messageEl = this.clearMessage();
433
					dom.append(messageEl, $('p', undefined, afterReplaceAllMessage));
434 435 436
				}, (error) => {
					progressRunner.done();
					errors.isPromiseCanceledError(error);
437
					this.notificationService.error(error);
438 439 440
				});
			}
		});
441 442
	}

443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
	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);
	}

507 508
	private clearMessage(): HTMLElement {
		this.searchWithoutFolderMessageElement = void 0;
509

510 511 512 513 514
		dom.clearNode(this.messagesElement);
		dom.show(this.messagesElement);
		dispose(this.messageDisposables);
		this.messageDisposables = [];

515
		return dom.append(this.messagesElement, $('.message'));
516 517
	}

518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
	private createSearchResultsView(container: HTMLElement): void {
		this.resultsElement = dom.append(container, $('.results.show-file-icons'));
		const dataSource = this._register(this.instantiationService.createInstance(SearchDataSource));
		const renderer = this._register(this.instantiationService.createInstance(SearchRenderer, this.getActionRunner(), this));
		const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, (obj: any) => obj instanceof FileMatch ? obj.resource() : void 0);

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

537 538 539 540 541 542 543 544
		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);
545
				}
546 547 548 549 550 551 552
				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 已提交
553

554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
		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 已提交
574 575
	}

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

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

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

		// Expand and go past FileMatch nodes
R
Rob Lourens 已提交
603
		while (!(next instanceof Match)) {
604 605
			if (!this.tree.isExpanded(next)) {
				this.tree.expand(next);
606 607 608
			}

			// Select the FileMatch's first child
609
			next = navigator.next();
610 611 612
		}

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

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

626
		let prev = navigator.previous();
627 628

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

645 646 647 648 649 650
			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();
651 652 653 654
			}
		}

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

S
Sandeep Somavarapu 已提交
664 665
	public setVisible(visible: boolean): Promise<void> {
		let promise: Promise<void>;
666
		this.viewletVisible.set(visible);
E
Erich Gamma 已提交
667
		if (visible) {
668 669 670 671 672 673
			if (this.changedWhileHidden) {
				// Render if results changed while viewlet was hidden - #37818
				this.refreshAndUpdateCount();
				this.changedWhileHidden = false;
			}

E
Erich Gamma 已提交
674 675 676 677 678 679 680 681 682
			promise = super.setVisible(visible);
			this.tree.onVisible();
		} else {
			this.tree.onHidden();
			promise = super.setVisible(visible);
		}

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

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

		return promise;
	}

Y
Yogesh 已提交
697 698 699 700
	public moveFocusToResults(): void {
		this.tree.domFocus();
	}

E
Erich Gamma 已提交
701 702 703
	public focus(): void {
		super.focus();

R
Rob Lourens 已提交
704 705 706 707 708
		const updatedText = this.updateTextFromSelection();
		this.searchWidget.focus(undefined, undefined, updatedText);
	}

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

718
				this.searchWidget.searchInput.setValue(selectedText);
719
				updatedText = true;
720
			}
721
		}
R
Rob Lourens 已提交
722

R
Rob Lourens 已提交
723
		return updatedText;
724 725
	}

726 727
	public focusNextInputBox(): void {
		if (this.searchWidget.searchInputHasFocus()) {
728 729 730 731 732
			if (this.searchWidget.isReplaceShown()) {
				this.searchWidget.focus(true, true);
			} else {
				this.moveFocusFromSearchOrReplace();
			}
733 734 735 736
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
737
			this.moveFocusFromSearchOrReplace();
738 739 740 741
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
742 743 744 745 746 747
			this.inputPatternExcludes.focus();
			this.inputPatternExcludes.select();
			return;
		}

		if (this.inputPatternExcludes.inputHasFocus()) {
748 749 750 751 752
			this.selectTreeIfNotSelected();
			return;
		}
	}

753 754
	private moveFocusFromSearchOrReplace() {
		if (this.showsFileTypes()) {
755
			this.toggleQueryDetails(true, this.showsFileTypes());
756 757 758 759 760
		} else {
			this.selectTreeIfNotSelected();
		}
	}

761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
	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;
		}

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

S
Sandeep Somavarapu 已提交
782 783 784 785
		if (this.tree.isDOMFocused()) {
			this.moveFocusFromResults();
			return;
		}
786 787
	}

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

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

801
		if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
802
			dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
803
		} else {
804
			dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
805 806
		}

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

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

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

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

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

		this.tree.layout(searchResultContainerSize);
	}

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

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

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

838 839 840 841
	public isSearching(): boolean {
		return this.searching;
	}

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

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

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

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

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

R
Rob Lourens 已提交
879 880 881 882
		if (dom.isAncestor(document.activeElement, this.getContainer())) {
			return null;
		}

883 884 885 886
		let activeTextEditorWidget = this.editorService.activeTextEditorWidget;
		if (isDiffEditor(activeTextEditorWidget)) {
			if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) {
				activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor();
887
			} else {
888
				activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
889 890 891
			}
		}

892
		if (!isCodeEditor(activeTextEditorWidget)) {
E
Erich Gamma 已提交
893 894 895
			return null;
		}

896
		const range = activeTextEditorWidget.getSelection();
R
Rob Lourens 已提交
897 898 899 900
		if (!range) {
			return null;
		}

R
Rob Lourens 已提交
901
		if (range.isEmpty() && !this.searchWidget.searchInput.getValue() && allowUnselectedWord) {
902
			const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition());
R
Rob Lourens 已提交
903 904 905 906 907 908
			if (wordAtPosition) {
				return wordAtPosition.word;
			}
		}

		if (!range.isEmpty() && range.startLineNumber === range.endLineNumber) {
909
			let searchText = activeTextEditorWidget.getModel().getLineContent(range.startLineNumber);
910 911
			searchText = searchText.substring(range.startColumn - 1, range.endColumn - 1);
			return searchText;
E
Erich Gamma 已提交
912
		}
913

E
Erich Gamma 已提交
914 915 916 917 918 919 920
		return null;
	}

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

S
Sandeep Somavarapu 已提交
921 922
	public toggleCaseSensitive(): void {
		this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive());
923
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
924 925 926 927
	}

	public toggleWholeWords(): void {
		this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords());
928
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
929 930 931 932
	}

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

936
	public toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean, reverse?: boolean): void {
E
Erich Gamma 已提交
937 938
		let cls = 'more';
		show = typeof show === 'undefined' ? !dom.hasClass(this.queryDetails, cls) : Boolean(show);
A
Amy Qiu 已提交
939
		this.viewletSettings['query.queryDetailsExpanded'] = show;
E
Erich Gamma 已提交
940 941 942
		skipLayout = Boolean(skipLayout);

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

E
Erich Gamma 已提交
962 963 964 965 966
		if (!skipLayout && this.size) {
			this.layout(this.size);
		}
	}

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

		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) {
983
						const owningRootName = owningFolder.name;
984 985

						// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
986
						const isUniqueFolder = workspace.folders.filter(folder => folder.name === owningRootName).length === 1;
987
						if (isUniqueFolder) {
988 989 990 991 992 993
							const relativePath = paths.normalize(pathToRelative(owningFolder.uri.fsPath, resource.fsPath));
							if (relativePath === '.') {
								folderPath = `./${owningFolder.name}`;
							} else {
								folderPath = `./${owningFolder.name}/${relativePath}`;
							}
994 995 996
						} else {
							folderPath = resource.fsPath;
						}
997 998
					}
				}
999 1000 1001 1002 1003

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

1006
		if (!folderPaths.length || folderPaths.some(folderPath => folderPath === '.')) {
S
Sandeep Somavarapu 已提交
1007 1008 1009 1010 1011
			this.inputPatternIncludes.setValue('');
			this.searchWidget.focus();
			return;
		}

1012
		// Show 'files to include' box
E
Erich Gamma 已提交
1013
		if (!this.showsFileTypes()) {
1014
			this.toggleQueryDetails(true, true);
E
Erich Gamma 已提交
1015
		}
B
Benjamin Pasero 已提交
1016

1017
		this.inputPatternIncludes.setValue(folderPaths.join(', '));
B
Benjamin Pasero 已提交
1018
		this.searchWidget.focus(false);
E
Erich Gamma 已提交
1019 1020
	}

1021
	public onQueryChanged(preserveFocus?: boolean): void {
1022 1023 1024 1025
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1026 1027 1028
		const excludePatternText = this.inputPatternExcludes.getValue().trim();
		const includePatternText = this.inputPatternIncludes.getValue().trim();
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
E
Erich Gamma 已提交
1029 1030 1031 1032 1033

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

1034
		// Validate regex is OK
E
Erich Gamma 已提交
1035 1036 1037 1038 1039
		if (isRegex) {
			let regExp: RegExp;
			try {
				regExp = new RegExp(contentPattern);
			} catch (e) {
1040
				return; // malformed regex
E
Erich Gamma 已提交
1041
			}
1042

E
Erich Gamma 已提交
1043
			if (strings.regExpLeadsToEndlessLoop(regExp)) {
1044
				return; // endless regex
E
Erich Gamma 已提交
1045 1046 1047
			}
		}

1048
		const content: IPatternInfo = {
E
Erich Gamma 已提交
1049 1050 1051
			pattern: contentPattern,
			isRegExp: isRegex,
			isCaseSensitive: isCaseSensitive,
1052
			isWordMatch: isWholeWords,
1053
			isSmartCase: this.configurationService.getValue<ISearchConfiguration>().search.smartCase
E
Erich Gamma 已提交
1054 1055
		};

1056 1057
		const excludePattern = this.inputPatternExcludes.getValue();
		const includePattern = this.inputPatternIncludes.getValue();
E
Erich Gamma 已提交
1058

R
Rob Lourens 已提交
1059 1060 1061
		// 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 已提交
1062
		const charsPerLine = content.isRegExp ? 10000 :
1063
			250;
R
Rob Lourens 已提交
1064

1065
		const options: IQueryOptions = {
1066
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
1067
			maxResults: SearchView.MAX_TEXT_RESULTS,
1068 1069
			disregardIgnoreFiles: !useExcludesAndIgnoreFiles,
			disregardExcludeSettings: !useExcludesAndIgnoreFiles,
R
Rob Lourens 已提交
1070
			excludePattern,
1071 1072
			includePattern,
			previewOptions: {
R
Rob Lourens 已提交
1073 1074
				matchLines: 1,
				charsPerLine
1075
			}
E
Erich Gamma 已提交
1076
		};
S
Sandeep Somavarapu 已提交
1077
		const folderResources = this.contextService.getWorkspace().folders;
1078 1079 1080 1081 1082 1083 1084 1085

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

		let query: ISearchQuery;
		try {
1086
			query = this.queryBuilder.text(content, folderResources.map(folder => folder.uri), options);
1087 1088
		} catch (err) {
			onQueryValidationError(err);
1089
			return;
1090
		}
1091

1092
		this.validateQuery(query).then(() => {
1093
			this.onQueryTriggered(query, excludePatternText, includePatternText);
1094

1095 1096 1097
			if (!preserveFocus) {
				this.searchWidget.focus(false); // focus back to input field
			}
1098
		}, onQueryValidationError);
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
	}

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

			return undefined;
		});
E
Erich Gamma 已提交
1121 1122
	}

R
Rob Lourens 已提交
1123
	private onQueryTriggered(query: ISearchQuery, excludePatternText: string, includePatternText: string): void {
1124
		this.inputPatternExcludes.onSearchSubmit();
1125 1126
		this.inputPatternIncludes.onSearchSubmit();

S
Sandeep Somavarapu 已提交
1127 1128
		this.viewModel.cancelSearch();

1129 1130 1131 1132 1133 1134 1135
		// 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 已提交
1136

1137
		this.searchWidget.searchInput.clearMessage();
1138 1139 1140 1141 1142 1143
		this.searching = true;
		setTimeout(() => {
			if (this.searching) {
				this.changeActionAtPosition(0, this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL));
			}
		}, 2000);
E
Erich Gamma 已提交
1144 1145
		this.showEmptyStage();

1146
		let onComplete = (completed?: ISearchComplete) => {
1147 1148
			this.searching = false;
			this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1149 1150 1151 1152 1153 1154 1155 1156

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

1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
			// 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;
1168
			});
1169

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

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

S
Sandeep Somavarapu 已提交
1174 1175
			this.searchSubmitted = true;
			this.updateActions();
E
Erich Gamma 已提交
1176

1177
			if (completed && completed.limitHit) {
1178
				this.searchWidget.searchInput.showMessage({
E
Erich Gamma 已提交
1179 1180 1181 1182 1183 1184
					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) {
1185 1186
				let hasExcludes = !!excludePatternText;
				let hasIncludes = !!includePatternText;
E
Erich Gamma 已提交
1187 1188
				let message: string;

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

1201 1202 1203
				// Indicate as status to ARIA
				aria.status(message);

E
Erich Gamma 已提交
1204
				this.tree.onHidden();
1205
				dom.hide(this.resultsElement);
1206 1207

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

1210
				if (!completed) {
1211
					const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again")));
1212
					this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
1213
						dom.EventHelper.stop(e, false);
1214
						this.onQueryChanged();
1215
					}));
1216
				} else if (hasIncludes || hasExcludes) {
1217
					const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files")));
1218
					this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
E
Erich Gamma 已提交
1219 1220
						dom.EventHelper.stop(e, false);

1221
						this.inputPatternExcludes.setValue('');
E
Erich Gamma 已提交
1222 1223
						this.inputPatternIncludes.setValue('');

1224
						this.onQueryChanged();
1225
					}));
E
Erich Gamma 已提交
1226
				} else {
1227
					const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings")));
1228
					this.messageDisposables.push(dom.addDisposableListener(openSettingsLink, dom.EventType.CLICK, (e: MouseEvent) => {
E
Erich Gamma 已提交
1229 1230
						dom.EventHelper.stop(e, false);

1231 1232 1233 1234
						const options: ISettingsEditorOptions = { query: '.exclude' };
						this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ?
							this.preferencesService.openWorkspaceSettings(undefined, options) :
							this.preferencesService.openGlobalSettings(undefined, options);
1235
					}));
E
Erich Gamma 已提交
1236
				}
1237 1238

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

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

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

1249
				if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
1250
					this.showSearchWithoutFolderMessage();
1251
				}
E
Erich Gamma 已提交
1252
			} else {
R
Rob Lourens 已提交
1253
				this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights
1254

1255
				// Indicate final search result count for ARIA
1256
				aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount()));
E
Erich Gamma 已提交
1257 1258 1259 1260
			}
		};

		let onError = (e: any) => {
1261 1262 1263
			if (errors.isPromiseCanceledError(e)) {
				onComplete(null);
			} else {
1264 1265
				this.searching = false;
				this.changeActionAtPosition(0, this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
1266
				progressRunner.done();
1267
				this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR });
1268
				this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
1269 1270 1271
			}
		};

1272 1273
		let total: number = 0;
		let worked: number = 0;
E
Erich Gamma 已提交
1274
		let visibleMatches = 0;
1275 1276 1277 1278 1279 1280 1281 1282 1283
		let onProgress = (p: ISearchProgressItem) => {
			// Progress
			if (p.total) {
				total = p.total;
			}
			if (p.worked) {
				worked = p.worked;
			}
		};
E
Erich Gamma 已提交
1284 1285

		// Handle UI updates in an interval to show frequent progress and results
1286
		let uiRefreshHandle: any = setInterval(() => {
1287
			if (!this.searching) {
E
Erich Gamma 已提交
1288 1289 1290 1291
				window.clearInterval(uiRefreshHandle);
				return;
			}

1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315
			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 已提交
1316
			// Search result tree update
1317 1318 1319
			const fileCount = this.viewModel.searchResult.fileCount();
			if (visibleMatches !== fileCount) {
				visibleMatches = fileCount;
1320
				this.tree.refresh();
1321

1322
				this.updateSearchResultCount();
1323
			}
1324
			if (fileCount > 0) {
S
Sandeep Somavarapu 已提交
1325
				this.updateActions();
E
Erich Gamma 已提交
1326
			}
1327
		}, 100);
E
Erich Gamma 已提交
1328

S
Sandeep Somavarapu 已提交
1329
		this.searchWidget.setReplaceAllActionState(false);
1330

1331
		this.viewModel.search(query, onProgress).then(onComplete, onError);
E
Erich Gamma 已提交
1332 1333
	}

1334
	private updateSearchResultCount(): void {
1335
		const fileCount = this.viewModel.searchResult.fileCount();
1336 1337
		this.hasSearchResultsKey.set(fileCount > 0);

1338
		const msgWasHidden = this.messagesElement.style.display === 'none';
1339
		if (fileCount > 0) {
1340
			const messageEl = this.clearMessage();
1341
			dom.append(messageEl, $('p', undefined, this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount)));
1342 1343 1344
			if (msgWasHidden) {
				this.reLayout();
			}
1345
		} else if (!msgWasHidden) {
1346
			dom.hide(this.messagesElement);
1347 1348 1349
		}
	}

1350 1351 1352 1353 1354 1355 1356
	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);
1357
		} else {
1358
			return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount);
1359 1360 1361
		}
	}

1362 1363 1364 1365
	private showSearchWithoutFolderMessage(): void {
		this.searchWithoutFolderMessageElement = this.clearMessage();

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

		const openFolderLink = dom.append(textEl,
1369
			$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
1370 1371 1372 1373 1374 1375

		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);
1376
			this.actionRunner.run(action).then(() => {
1377 1378 1379 1380
				action.dispose();
			}, err => {
				action.dispose();
				errors.onUnexpectedError(err);
1381
			});
1382
		}));
1383 1384
	}

E
Erich Gamma 已提交
1385
	private showEmptyStage(): void {
1386

E
Erich Gamma 已提交
1387
		// disable 'result'-actions
S
Sandeep Somavarapu 已提交
1388 1389
		this.searchSubmitted = false;
		this.updateActions();
E
Erich Gamma 已提交
1390 1391

		// clean up ui
S
Sandeep Somavarapu 已提交
1392
		// this.replaceService.disposeAllReplacePreviews();
1393
		dom.hide(this.messagesElement);
1394
		dom.show(this.resultsElement);
E
Erich Gamma 已提交
1395
		this.tree.onVisible();
S
Sandeep Somavarapu 已提交
1396
		this.currentSelectedFileMatch = null;
E
Erich Gamma 已提交
1397 1398
	}

S
Sandeep Somavarapu 已提交
1399
	private onFocus(lineMatch: any, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
E
Erich Gamma 已提交
1400
		if (!(lineMatch instanceof Match)) {
1401
			this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
A
Alex Dima 已提交
1402
			return TPromise.as(true);
E
Erich Gamma 已提交
1403 1404
		}

1405 1406
		const useReplacePreview = this.configurationService.getValue<ISearchConfiguration>().search.useReplacePreview;
		return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
R
Rob Lourens 已提交
1407 1408
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1409 1410 1411
	}

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

			return this.editorGroupsService.activateGroup(editor.group);
S
Sandeep Somavarapu 已提交
1433
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1434 1435
	}

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

B
Benjamin Pasero 已提交
1460
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1461 1462 1463 1464
		if (!this.viewModel) {
			return;
		}

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

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

1481
		let matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1482 1483 1484

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

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

1494
	private changeActionAtPosition(index: number, newAction: ClearSearchResultsAction | CancelSearchAction | RefreshAction | CollapseDeepestExpandedLevelAction): void {
1495 1496 1497 1498
		this.actions.splice(index, 1, newAction);
		this.updateTitleArea();
	}

1499
	private clearHistory(): void {
1500 1501 1502 1503 1504
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

A
Amy Qiu 已提交
1505
	public shutdown(): void {
A
Amy Qiu 已提交
1506 1507 1508 1509
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1510
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1511
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1512
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1513 1514 1515 1516 1517 1518

		// store memento
		this.viewletSettings['query.contentPattern'] = contentPattern;
		this.viewletSettings['query.regex'] = isRegex;
		this.viewletSettings['query.wholeWords'] = isWholeWords;
		this.viewletSettings['query.caseSensitive'] = isCaseSensitive;
1519
		this.viewletSettings['query.folderExclusions'] = patternExcludes;
A
Amy Qiu 已提交
1520
		this.viewletSettings['query.folderIncludes'] = patternIncludes;
1521
		this.viewletSettings['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles;
A
Amy Qiu 已提交
1522

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

1528
		this.searchHistoryService.save({
1529 1530 1531 1532
			search: searchHistory,
			replace: replaceHistory,
			exclude: patternExcludesHistory,
			include: patternIncludesHistory
1533 1534
		});

A
Amy Qiu 已提交
1535 1536 1537
		super.shutdown();
	}

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

		super.dispose();
	}
1543 1544 1545
}

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

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

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

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

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

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

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