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

6
import * as browser from 'vs/base/browser/browser';
7
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
8
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
9
import * as aria from 'vs/base/browser/ui/aria/aria';
10
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
R
Rob Lourens 已提交
11 12
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree';
13 14 15
import { IAction } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
R
Rob Lourens 已提交
16 17
import { Emitter, Event } from 'vs/base/common/event';
import { Iterator } from 'vs/base/common/iterator';
S
Sandeep Somavarapu 已提交
18
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
19
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
20
import * as extpath from 'vs/base/common/extpath';
21 22
import * as env from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
23
import { URI } from 'vs/base/common/uri';
24
import 'vs/css!./media/searchview';
25 26 27
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as nls from 'vs/nls';
R
Rob Lourens 已提交
28 29
import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
J
Johannes Rieken 已提交
30
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
31
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
R
Rob Lourens 已提交
32
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
33 34
import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
35
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
R
Rob Lourens 已提交
36
import { TreeResourceNavigator2, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
37
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
J
Johannes Rieken 已提交
38
import { IProgressService } from 'vs/platform/progress/common/progress';
39
import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ISearchHistoryService, ISearchHistoryValues, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/workbench/services/search/common/search';
B
Benjamin Pasero 已提交
40
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
J
Johannes Rieken 已提交
41
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
42
import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
43 44 45
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';
46
import { ResourceLabels } from 'vs/workbench/browser/labels';
47
import { Viewlet } from 'vs/workbench/browser/viewlet';
48
import { IEditor } from 'vs/workbench/common/editor';
49 50
import { IPanel } from 'vs/workbench/common/panel';
import { IViewlet } from 'vs/workbench/common/viewlet';
51 52 53 54 55 56 57 58 59
import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, getKeyboardEventForEditorOpen, RefreshAction } from 'vs/workbench/contrib/search/browser/searchActions';
import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView';
import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { IReplaceService } from 'vs/workbench/contrib/search/common/replace';
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search';
import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel';
60
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
61
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
S
Sandeep Somavarapu 已提交
62
import { IPartService } from 'vs/workbench/services/part/common/partService';
63
import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
64
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
E
Erich Gamma 已提交
65

66 67
const $ = dom.$;

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

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

72
	private static readonly WIDE_CLASS_NAME = 'wide';
R
Rob Lourens 已提交
73
	private static readonly WIDE_VIEW_SIZE = 1000;
74
	private static readonly ACTIONS_RIGHT_CLASS_NAME = 'actions-right';
75

E
Erich Gamma 已提交
76
	private isDisposed: boolean;
77

E
Erich Gamma 已提交
78
	private queryBuilder: QueryBuilder;
79
	private viewModel: SearchModel;
E
Erich Gamma 已提交
80

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

S
Sandeep Somavarapu 已提交
94
	private searchSubmitted: boolean;
95
	private searching: boolean;
S
Sandeep Somavarapu 已提交
96

97
	private actions: Array<CollapseDeepestExpandedLevelAction | ClearSearchResultsAction> = [];
98 99
	private cancelAction: CancelSearchAction;
	private refreshAction: RefreshAction;
R
Rob Lourens 已提交
100
	private contextMenu: IMenu;
101

R
Rob Lourens 已提交
102
	private tree: WorkbenchObjectTree<RenderableMatch>;
B
Benjamin Pasero 已提交
103
	private treeLabels: ResourceLabels;
B
Benjamin Pasero 已提交
104
	private viewletState: object;
105
	private globalMemento: object;
106 107
	private messagesElement: HTMLElement;
	private messageDisposables: IDisposable[] = [];
108
	private searchWidgetsContainerElement: HTMLElement;
109
	private searchWidget: SearchWidget;
110
	private size: dom.Dimension;
E
Erich Gamma 已提交
111
	private queryDetails: HTMLElement;
S
Sandeep Somavarapu 已提交
112
	private toggleQueryDetailsButton: HTMLElement;
113 114
	private inputPatternExcludes: ExcludePatternInputWidget;
	private inputPatternIncludes: PatternInputWidget;
115
	private resultsElement: HTMLElement;
E
Erich Gamma 已提交
116

S
Sandeep Somavarapu 已提交
117 118
	private currentSelectedFileMatch: FileMatch;

M
Matt Bierner 已提交
119
	private readonly selectCurrentMatchEmitter: Emitter<string>;
S
Sandeep Somavarapu 已提交
120
	private delayedRefresh: Delayer<void>;
121
	private changedWhileHidden: boolean;
R
Rob Lourens 已提交
122

123
	private searchWithoutFolderMessageElement: HTMLElement;
124

J
Johannes Rieken 已提交
125
	private currentSearchQ = Promise.resolve();
126

127
	constructor(
S
Sandeep Somavarapu 已提交
128
		@IPartService partService: IPartService,
129
		@ITelemetryService telemetryService: ITelemetryService,
130 131 132 133 134
		@IFileService private readonly fileService: IFileService,
		@IEditorService private readonly editorService: IEditorService,
		@IProgressService private readonly progressService: IProgressService,
		@INotificationService private readonly notificationService: INotificationService,
		@IDialogService private readonly dialogService: IDialogService,
B
Benjamin Pasero 已提交
135
		@IStorageService storageService: IStorageService,
136 137
		@IContextViewService private readonly contextViewService: IContextViewService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
138
		@IConfigurationService configurationService: IConfigurationService,
139 140 141 142 143 144
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
		@ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService,
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IReplaceService private readonly replaceService: IReplaceService,
		@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
		@IPreferencesService private readonly preferencesService: IPreferencesService,
145
		@IThemeService protected themeService: IThemeService,
146 147 148
		@ISearchHistoryService private readonly searchHistoryService: ISearchHistoryService,
		@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
		@IContextMenuService private readonly contextMenuService: IContextMenuService,
149
		@IMenuService private readonly menuService: IMenuService
E
Erich Gamma 已提交
150
	) {
151
		super(VIEW_ID, configurationService, partService, telemetryService, themeService, storageService);
E
Erich Gamma 已提交
152

I
isidor 已提交
153
		this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService);
S
Sandeep Somavarapu 已提交
154
		this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService);
155 156
		this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
		this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService);
157
		this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService);
158 159
		this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService);
		this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService);
160
		this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService);
161
		this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService);
162
		this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService);
163
		this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
164
		this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
E
Erich Gamma 已提交
165 166

		this.queryBuilder = this.instantiationService.createInstance(QueryBuilder);
B
Benjamin Pasero 已提交
167
		this.viewletState = this.getMemento(StorageScope.WORKSPACE);
168
		this.globalMemento = this.getMemento(StorageScope.GLOBAL);
E
Erich Gamma 已提交
169

B
Benjamin Pasero 已提交
170 171 172
		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()));
173
		this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
174

175
		this.selectCurrentMatchEmitter = this._register(new Emitter<string>());
J
Joao Moreno 已提交
176
		this._register(Event.debounce(this.selectCurrentMatchEmitter.event, (l, e) => e, 100, /*leading=*/true)
177
			(() => this.selectCurrentMatch()));
R
Rob Lourens 已提交
178

179
		this.delayedRefresh = this._register(new Delayer<void>(250));
180

E
Erich Gamma 已提交
181 182
	}

R
Rob Lourens 已提交
183
	get searchResult(): SearchResult {
R
Rob Lourens 已提交
184 185 186
		return this.viewModel && this.viewModel.searchResult;
	}

187
	private onDidChangeWorkbenchState(): void {
188 189
		if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.searchWithoutFolderMessageElement) {
			dom.hide(this.searchWithoutFolderMessageElement);
190 191 192
		}
	}

R
Rob Lourens 已提交
193
	create(parent: HTMLElement): void {
E
Erich Gamma 已提交
194 195
		super.create(parent);

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

199
		this.searchWidgetsContainerElement = dom.append(parent, $('.search-widgets-container'));
200
		this.createSearchWidget(this.searchWidgetsContainerElement);
201

202
		const history = this.searchHistoryService.load();
B
Benjamin Pasero 已提交
203 204
		const filePatterns = this.viewletState['query.filePatterns'] || '';
		const patternExclusions = this.viewletState['query.folderExclusions'] || '';
R
Rob Lourens 已提交
205
		const patternExclusionsHistory: string[] = history.exclude || [];
B
Benjamin Pasero 已提交
206
		const patternIncludes = this.viewletState['query.folderIncludes'] || '';
R
Rob Lourens 已提交
207
		const patternIncludesHistory: string[] = history.include || [];
B
Benjamin Pasero 已提交
208 209 210
		const queryDetailsExpanded = this.viewletState['query.queryDetailsExpanded'] || '';
		const useExcludesAndIgnoreFiles = typeof this.viewletState['query.useExcludesAndIgnoreFiles'] === 'boolean' ?
			this.viewletState['query.useExcludesAndIgnoreFiles'] : true;
211

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

214 215 216
		// 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 已提交
217

218 219
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => {
			dom.EventHelper.stop(e);
220
			this.toggleQueryDetails(!this.isScreenReaderOptimized());
221 222 223
		}));
		this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
			const event = new StandardKeyboardEvent(e);
E
Erich Gamma 已提交
224

225 226 227 228 229 230 231
			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 已提交
232

233 234 235 236 237 238 239 240 241
			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 已提交
242

243 244 245 246 247
		// 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));
248

249 250 251 252
		this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, {
			ariaLabel: nls.localize('label.includes', 'Search Include Patterns'),
			history: patternIncludesHistory,
		}));
253

254
		this.inputPatternIncludes.setValue(patternIncludes);
255

256
		this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true));
257 258 259 260 261 262 263 264 265 266 267
		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,
		}));
268

269 270
		this.inputPatternExcludes.setValue(patternExclusions);
		this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles);
271

272
		this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true));
273 274 275
		this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget
		this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused);

276
		this.messagesElement = dom.append(parent, $('.messages'));
277
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
278
			this.showSearchWithoutFolderMessage();
279
		}
280

281
		this.createSearchResultsView(parent);
282

S
Sandeep Somavarapu 已提交
283
		this.actions = [
284 285
			this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)),
			this._register(this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL))
S
Sandeep Somavarapu 已提交
286
		];
287 288
		this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL));
		this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL));
289

290
		if (filePatterns !== '' || patternExclusions !== '' || patternIncludes !== '' || queryDetailsExpanded !== '' || !useExcludesAndIgnoreFiles) {
291
			this.toggleQueryDetails(true, true, true);
292 293
		}

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

296
		this._register(this.searchWidget.searchInput.onInput(() => this.updateActions()));
297
		this._register(this.searchWidget.replaceInput.onDidChange(() => this.updateActions()));
298 299
		this._register(this.searchIncludePattern.inputBox.onDidChange(() => this.updateActions()));
		this._register(this.searchExcludePattern.inputBox.onDidChange(() => this.updateActions()));
300

S
Sandeep Somavarapu 已提交
301 302
		this._register(this.onDidFocus(() => this.viewletFocused.set(true)));
		this._register(this.onDidBlur(() => this.viewletFocused.set(false)));
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

		this._register(this.onDidChangeVisibility(visible => this.onVisibilityChanged(visible)));
	}

	private onVisibilityChanged(visible: boolean): void {
		this.viewletVisible.set(visible);
		if (visible) {
			if (this.changedWhileHidden) {
				// Render if results changed while viewlet was hidden - #37818
				this.refreshAndUpdateCount();
				this.changedWhileHidden = false;
			}
		}

		// Enable highlights if there are searchresults
		if (this.viewModel) {
			this.viewModel.searchResult.toggleHighlights(visible);
		}

		// Open focused element from results in case the editor area is otherwise empty
		if (visible && !this.editorService.activeEditor) {
324
			const focus = this.tree.getFocus();
325 326 327 328
			if (focus) {
				this.onFocus(focus, true);
			}
		}
329 330
	}

R
Rob Lourens 已提交
331
	get searchAndReplaceWidget(): SearchWidget {
332 333 334
		return this.searchWidget;
	}

R
Rob Lourens 已提交
335
	get searchIncludePattern(): PatternInputWidget {
336 337 338
		return this.inputPatternIncludes;
	}

R
Rob Lourens 已提交
339
	get searchExcludePattern(): PatternInputWidget {
340 341 342
		return this.inputPatternExcludes;
	}

S
Sandeep Somavarapu 已提交
343 344 345
	private updateActions(): void {
		for (const action of this.actions) {
			action.update();
346 347
			this.refreshAction.update();
			this.cancelAction.update();
S
Sandeep Somavarapu 已提交
348 349 350
		}
	}

351 352 353 354 355 356
	private isScreenReaderOptimized() {
		const detected = browser.getAccessibilitySupport() === env.AccessibilitySupport.Enabled;
		const config = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
		return config === 'on' || (config === 'auto' && detected);
	}

357
	private createSearchWidget(container: HTMLElement): void {
358
		const contentPattern = this.viewletState['query.contentPattern'] || '';
359
		const replaceText = this.viewletState['query.replaceText'] || '';
360 361 362
		const isRegex = this.viewletState['query.regex'] === true;
		const isWholeWords = this.viewletState['query.wholeWords'] === true;
		const isCaseSensitive = this.viewletState['query.caseSensitive'] === true;
363
		const history = this.searchHistoryService.load();
364 365 366
		const searchHistory = history.search || this.viewletState['query.searchHistory'] || [];
		const replaceHistory = history.replace || this.viewletState['query.replaceHistory'] || [];
		const showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true;
E
Erich Gamma 已提交
367

368
		this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, <ISearchWidgetOptions>{
369
			value: contentPattern,
370
			replaceValue: replaceText,
371 372
			isRegex: isRegex,
			isCaseSensitive: isCaseSensitive,
373
			isWholeWords: isWholeWords,
374
			searchHistory: searchHistory,
375
			replaceHistory: replaceHistory
B
Benjamin Pasero 已提交
376
		}));
S
Sandeep Somavarapu 已提交
377

B
Benjamin Pasero 已提交
378
		if (showReplace) {
S
Sandeep Somavarapu 已提交
379 380 381
			this.searchWidget.toggleReplace(true);
		}

382
		this._register(this.searchWidget.onSearchSubmit(() => this.onQueryChanged()));
B
Benjamin Pasero 已提交
383
		this._register(this.searchWidget.onSearchCancel(() => this.cancelSearch()));
384
		this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true)));
385

386 387 388
		this._register(this.searchWidget.onDidHeightChange(() => this.reLayout()));

		this._register(this.searchWidget.onReplaceToggled(() => this.reLayout()));
B
Benjamin Pasero 已提交
389
		this._register(this.searchWidget.onReplaceStateChange((state) => {
J
Johannes Rieken 已提交
390
			this.viewModel.replaceActive = state;
R
Rob Lourens 已提交
391
			this.refreshTree();
S
Sandeep Somavarapu 已提交
392
		}));
B
Benjamin Pasero 已提交
393
		this._register(this.searchWidget.onReplaceValueChanged((value) => {
J
Johannes Rieken 已提交
394
			this.viewModel.replaceString = this.searchWidget.getReplaceValue();
R
Rob Lourens 已提交
395
			this.delayedRefresh.trigger(() => this.refreshTree());
S
Sandeep Somavarapu 已提交
396
		}));
397

B
Benjamin Pasero 已提交
398
		this._register(this.searchWidget.onBlur(() => {
S
Sandeep Somavarapu 已提交
399 400 401
			this.toggleQueryDetailsButton.focus();
		}));

B
Benjamin Pasero 已提交
402
		this._register(this.searchWidget.onReplaceAll(() => this.replaceAll()));
403

404 405 406 407
		this.trackInputBox(this.searchWidget.searchInputFocusTracker);
		this.trackInputBox(this.searchWidget.replaceInputFocusTracker);
	}

408
	private trackInputBox(inputFocusTracker: dom.IFocusTracker, contextKey?: IContextKey<boolean>): void {
B
Benjamin Pasero 已提交
409
		this._register(inputFocusTracker.onDidFocus(() => {
410
			this.inputBoxFocused.set(true);
411 412 413
			if (contextKey) {
				contextKey.set(true);
			}
414
		}));
B
Benjamin Pasero 已提交
415
		this._register(inputFocusTracker.onDidBlur(() => {
416
			this.inputBoxFocused.set(this.searchWidget.searchInputHasFocus()
417
				|| this.searchWidget.replaceInputHasFocus()
418 419
				|| this.inputPatternIncludes.inputHasFocus()
				|| this.inputPatternExcludes.inputHasFocus());
420 421 422
			if (contextKey) {
				contextKey.set(false);
			}
423
		}));
S
Sandeep Somavarapu 已提交
424 425
	}

R
Rob Lourens 已提交
426
	private onSearchResultsChanged(event?: IChangeEvent): void {
427 428 429 430 431 432 433
		if (this.isVisible()) {
			return this.refreshAndUpdateCount(event);
		} else {
			this.changedWhileHidden = true;
		}
	}

R
Rob Lourens 已提交
434
	private refreshAndUpdateCount(event?: IChangeEvent): void {
R
Rob Lourens 已提交
435 436 437
		this.searchWidget.setReplaceAllActionState(!this.viewModel.searchResult.isEmpty());
		this.updateSearchResultCount(this.viewModel.searchResult.query.userDisabledExcludesAndIgnoreFiles);
		return this.refreshTree(event);
438 439
	}

R
Rob Lourens 已提交
440
	refreshTree(event?: IChangeEvent): void {
441
		const collapseResults = this.configurationService.getValue<ISearchConfigurationProperties>('search').collapseResults;
442
		if (!event || event.added || event.removed) {
443
			this.tree.setChildren(null, this.createResultIterator(collapseResults));
444
		} else {
R
Rob Lourens 已提交
445
			event.elements.forEach(element => {
446 447
				if (element instanceof FolderMatch) {
					// The folder may or may not be in the tree. Refresh the whole thing.
448
					this.tree.setChildren(null, this.createResultIterator(collapseResults));
449 450 451
					return;
				}

R
Rob Lourens 已提交
452
				const root = element instanceof SearchResult ? null : element;
453
				this.tree.setChildren(root, this.createIterator(element, collapseResults));
R
Rob Lourens 已提交
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
	private createResultIterator(collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator<ITreeElement<RenderableMatch>> {
		const folderMatches = this.searchResult.folderMatches()
			.filter(fm => !fm.isEmpty())
			.sort(searchMatchComparer);

		if (folderMatches.length === 1) {
			return this.createFolderIterator(folderMatches[0], collapseResults);
		}

		const foldersIt = Iterator.fromArray(folderMatches);
		return Iterator.map(foldersIt, folderMatch => {
			const children = this.createFolderIterator(folderMatch, collapseResults);
			return <ITreeElement<RenderableMatch>>{ element: folderMatch, children };
		});
	}

	private createFolderIterator(folderMatch: FolderMatch, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator<ITreeElement<RenderableMatch>> {
		const filesIt = Iterator.fromArray(
			folderMatch.matches()
				.sort(searchMatchComparer));

		return Iterator.map(filesIt, fileMatch => {
			const children = this.createFileIterator(fileMatch);

			let nodeExists = true;
			try { this.tree.getNode(fileMatch); } catch (e) { nodeExists = false; }

			const collapsed = nodeExists ? undefined :
				(collapseResults === 'alwaysCollapse' || (fileMatch.matches().length > 10 && collapseResults !== 'alwaysExpand'));

			return <ITreeElement<RenderableMatch>>{ element: fileMatch, children, collapsed };
		});
	}

	private createFileIterator(fileMatch: FileMatch): Iterator<ITreeElement<RenderableMatch>> {
		const matchesIt = Iterator.from(
			fileMatch.matches()
				.sort(searchMatchComparer));
		return Iterator.map(matchesIt, r => (<ITreeElement<RenderableMatch>>{ element: r }));
	}

	private createIterator(match: FolderMatch | FileMatch | SearchResult, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator<ITreeElement<RenderableMatch>> {
		return match instanceof SearchResult ? this.createResultIterator(collapseResults) :
			match instanceof FolderMatch ? this.createFolderIterator(match, collapseResults) :
				this.createFileIterator(match);
	}

505
	private replaceAll(): void {
506
		if (this.viewModel.searchResult.count() === 0) {
S
Sandeep Somavarapu 已提交
507 508 509
			return;
		}

510
		const progressRunner = this.progressService.show(100);
511

512 513 514 515
		const occurrences = this.viewModel.searchResult.count();
		const fileCount = this.viewModel.searchResult.fileCount();
		const replaceValue = this.searchWidget.getReplaceValue() || '';
		const afterReplaceAllMessage = this.buildAfterReplaceAllMessage(occurrences, fileCount, replaceValue);
516

517
		const confirmation: IConfirmation = {
518
			title: nls.localize('replaceAll.confirmation.title', "Replace All"),
519
			message: this.buildReplaceAllConfirmationMessage(occurrences, fileCount, replaceValue),
520
			primaryButton: nls.localize('replaceAll.confirm.button', "&&Replace"),
B
Benjamin Pasero 已提交
521
			type: 'question'
522 523
		};

524
		this.dialogService.confirm(confirmation).then(res => {
525
			if (res.confirmed) {
526 527 528
				this.searchWidget.setReplaceAllActionState(false);
				this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
					progressRunner.done();
529
					const messageEl = this.clearMessage();
530
					dom.append(messageEl, $('p', undefined, afterReplaceAllMessage));
531 532 533
				}, (error) => {
					progressRunner.done();
					errors.isPromiseCanceledError(error);
534
					this.notificationService.error(error);
535 536 537
				});
			}
		});
538 539
	}

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
	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);
	}

604
	private clearMessage(): HTMLElement {
R
Rob Lourens 已提交
605
		this.searchWithoutFolderMessageElement = undefined;
606

607 608 609 610 611
		dom.clearNode(this.messagesElement);
		dom.show(this.messagesElement);
		dispose(this.messageDisposables);
		this.messageDisposables = [];

612
		return dom.append(this.messagesElement, $('.message'));
613 614
	}

615 616
	private createSearchResultsView(container: HTMLElement): void {
		this.resultsElement = dom.append(container, $('.results.show-file-icons'));
R
Rob Lourens 已提交
617 618
		const delegate = this.instantiationService.createInstance(SearchDelegate);

619 620 621 622 623 624
		const identityProvider: IIdentityProvider<RenderableMatch> = {
			getId(element: RenderableMatch) {
				return element.id();
			}
		};

625
		this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
B
Benjamin Pasero 已提交
626
		this.tree = this._register(<WorkbenchObjectTree<RenderableMatch, any>>this.instantiationService.createInstance(WorkbenchObjectTree,
R
Rob Lourens 已提交
627 628 629
			this.resultsElement,
			delegate,
			[
B
Benjamin Pasero 已提交
630 631
				this._register(this.instantiationService.createInstance(FolderMatchRenderer, this.viewModel, this, this.treeLabels)),
				this._register(this.instantiationService.createInstance(FileMatchRenderer, this.viewModel, this, this.treeLabels)),
632
				this._register(this.instantiationService.createInstance(MatchRenderer, this.viewModel, this)),
R
Rob Lourens 已提交
633
			],
634
			{
635
				identityProvider,
636 637
				accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel),
				dnd: this.instantiationService.createInstance(SearchDND)
B
Benjamin Pasero 已提交
638
			}));
R
Rob Lourens 已提交
639
		this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
R
Rob Lourens 已提交
640 641

		const resourceNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: true }));
J
Joao Moreno 已提交
642
		this._register(Event.debounce(resourceNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => {
643
			if (options.element instanceof Match) {
644
				const selectedMatch: Match = options.element;
645 646
				if (this.currentSelectedFileMatch) {
					this.currentSelectedFileMatch.setSelectedMatch(null);
647
				}
648 649
				this.currentSelectedFileMatch = selectedMatch.parent();
				this.currentSelectedFileMatch.setSelectedMatch(selectedMatch);
R
Rob Lourens 已提交
650

R
Rob Lourens 已提交
651
				this.onFocus(selectedMatch, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned);
652 653
			}
		}));
S
Sandeep Somavarapu 已提交
654

J
Joao Moreno 已提交
655
		this._register(Event.any<any>(this.tree.onDidFocus, this.tree.onDidChangeFocus)(() => {
656
			if (this.tree.isDOMFocused()) {
R
Rob Lourens 已提交
657 658
				const focus = this.tree.getFocus()[0];
				this.firstMatchFocused.set(this.tree.navigate().first() === focus);
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
				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 已提交
675 676
	}

R
Rob Lourens 已提交
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
	private onContextMenu(e: ITreeContextMenuEvent<RenderableMatch>): void {
		if (!e.element) {
			return;
		}

		if (!this.contextMenu) {
			this.contextMenu = this._register(this.menuService.createMenu(MenuId.SearchContext, this.contextKeyService));
		}

		e.browserEvent.preventDefault();
		e.browserEvent.stopPropagation();

		this.contextMenuService.showContextMenu({
			getAnchor: () => e.anchor,
			getActions: () => {
				const actions: IAction[] = [];
				fillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService);
				return actions;
			},
			getActionsContext: () => e.element
		});
	}

R
Rob Lourens 已提交
700
	selectCurrentMatch(): void {
R
Rob Lourens 已提交
701
		const focused = this.tree.getFocus()[0];
R
Rob Lourens 已提交
702 703
		const fakeKeyboardEvent = getKeyboardEventForEditorOpen({ preserveFocus: false });
		this.tree.setSelection([focused], fakeKeyboardEvent);
R
Rob Lourens 已提交
704 705
	}

R
Rob Lourens 已提交
706
	selectNextMatch(): void {
R
Rob Lourens 已提交
707
		const [selected]: RenderableMatch[] = this.tree.getSelection();
708

R
Rob Lourens 已提交
709 710
		// Expand the initial selected node, if needed
		if (selected instanceof FileMatch) {
R
Rob Lourens 已提交
711 712
			if (this.tree.isCollapsed(selected)) {
				this.tree.expand(selected);
R
Rob Lourens 已提交
713 714 715
			}
		}

R
Rob Lourens 已提交
716
		let navigator = this.tree.navigate(selected);
R
Rob Lourens 已提交
717 718

		let next = navigator.next();
719
		if (!next) {
R
Rob Lourens 已提交
720
			// Reached the end - get a new navigator from the root.
R
Rob Lourens 已提交
721
			navigator = this.tree.navigate();
R
Rob Lourens 已提交
722
			next = navigator.first();
723 724 725
		}

		// Expand and go past FileMatch nodes
R
Rob Lourens 已提交
726
		while (!(next instanceof Match)) {
R
Rob Lourens 已提交
727 728
			if (this.tree.isCollapsed(next)) {
				this.tree.expand(next);
729 730 731
			}

			// Select the FileMatch's first child
732
			next = navigator.next();
733 734 735
		}

		// Reveal the newly selected element
736
		if (next) {
R
Rob Lourens 已提交
737
			this.tree.setFocus([next]);
R
Rob Lourens 已提交
738
			this.tree.setSelection([next]);
739
			this.tree.reveal(next);
740
			this.selectCurrentMatchEmitter.fire(undefined);
741
		}
742 743
	}

R
Rob Lourens 已提交
744
	selectPreviousMatch(): void {
R
Rob Lourens 已提交
745 746
		const [selected]: RenderableMatch[] = this.tree.getSelection();
		let navigator = this.tree.navigate(selected);
747

748
		let prev = navigator.previous();
749 750

		// Expand and go past FileMatch nodes
751 752
		if (!(prev instanceof Match)) {
			prev = navigator.previous();
R
Rob Lourens 已提交
753
			if (!prev) {
R
Rob Lourens 已提交
754
				// Wrap around
R
Rob Lourens 已提交
755 756 757 758
				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
R
Rob Lourens 已提交
759 760
				this.tree.expand(prev);
				let tmp: RenderableMatch;
R
Rob Lourens 已提交
761 762 763 764 765
				while (tmp = navigator.next()) {
					prev = tmp;
				}
			}

766 767 768
			if (!(prev instanceof Match)) {
				// There is a second non-Match result, which must be a collapsed FileMatch.
				// Expand it then select its last child.
R
Rob Lourens 已提交
769 770 771
				const nextItem = navigator.next();
				this.tree.expand(prev);
				navigator = this.tree.navigate(nextItem); // recreate navigator because modifying the tree can invalidate it
772
				prev = navigator.previous();
773 774 775 776
			}
		}

		// Reveal the newly selected element
777
		if (prev) {
R
Rob Lourens 已提交
778
			this.tree.setFocus([prev]);
R
Rob Lourens 已提交
779
			this.tree.setSelection([prev]);
R
Rob Lourens 已提交
780
			this.tree.reveal(prev);
781
			this.selectCurrentMatchEmitter.fire(undefined);
782
		}
783 784
	}

R
Rob Lourens 已提交
785
	moveFocusToResults(): void {
Y
Yogesh 已提交
786 787 788
		this.tree.domFocus();
	}

R
Rob Lourens 已提交
789
	focus(): void {
E
Erich Gamma 已提交
790 791
		super.focus();

R
Rob Lourens 已提交
792 793 794 795
		const updatedText = this.updateTextFromSelection();
		this.searchWidget.focus(undefined, undefined, updatedText);
	}

R
Rob Lourens 已提交
796
	updateTextFromSelection(allowUnselectedWord = true): boolean {
797
		let updatedText = false;
798 799
		const seedSearchStringFromSelection = this.configurationService.getValue<IEditorOptions>('editor').find.seedSearchStringFromSelection;
		if (seedSearchStringFromSelection) {
R
Rob Lourens 已提交
800
			let selectedText = this.getSearchTextFromEditor(allowUnselectedWord);
801
			if (selectedText) {
802 803 804 805
				if (this.searchWidget.searchInput.getRegex()) {
					selectedText = strings.escapeRegExpCharacters(selectedText);
				}

806
				this.searchWidget.searchInput.setValue(selectedText);
807
				updatedText = true;
808
			}
809
		}
R
Rob Lourens 已提交
810

R
Rob Lourens 已提交
811
		return updatedText;
812 813
	}

R
Rob Lourens 已提交
814
	focusNextInputBox(): void {
815
		if (this.searchWidget.searchInputHasFocus()) {
816 817 818 819 820
			if (this.searchWidget.isReplaceShown()) {
				this.searchWidget.focus(true, true);
			} else {
				this.moveFocusFromSearchOrReplace();
			}
821 822 823 824
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
825
			this.moveFocusFromSearchOrReplace();
826 827 828 829
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
830 831 832 833 834 835
			this.inputPatternExcludes.focus();
			this.inputPatternExcludes.select();
			return;
		}

		if (this.inputPatternExcludes.inputHasFocus()) {
836 837 838 839 840
			this.selectTreeIfNotSelected();
			return;
		}
	}

841 842
	private moveFocusFromSearchOrReplace() {
		if (this.showsFileTypes()) {
843
			this.toggleQueryDetails(true, this.showsFileTypes());
844 845 846 847 848
		} else {
			this.selectTreeIfNotSelected();
		}
	}

R
Rob Lourens 已提交
849
	focusPreviousInputBox(): void {
850 851 852 853 854 855 856 857 858 859 860 861 862 863
		if (this.searchWidget.searchInputHasFocus()) {
			return;
		}

		if (this.searchWidget.replaceInputHasFocus()) {
			this.searchWidget.focus(true);
			return;
		}

		if (this.inputPatternIncludes.inputHasFocus()) {
			this.searchWidget.focus(true, true);
			return;
		}

864 865 866 867 868 869
		if (this.inputPatternExcludes.inputHasFocus()) {
			this.inputPatternIncludes.focus();
			this.inputPatternIncludes.select();
			return;
		}

S
Sandeep Somavarapu 已提交
870 871 872 873
		if (this.tree.isDOMFocused()) {
			this.moveFocusFromResults();
			return;
		}
874 875
	}

S
Sandeep Somavarapu 已提交
876
	private moveFocusFromResults(): void {
877
		if (this.showsFileTypes()) {
878
			this.toggleQueryDetails(true, true, false, true);
879 880 881
		} else {
			this.searchWidget.focus(true, true);
		}
E
Erich Gamma 已提交
882 883 884 885 886 887 888
	}

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

R
Rob Lourens 已提交
889
		const actionsPosition = this.configurationService.getValue<ISearchConfigurationProperties>('search').actionsPosition;
890 891
		dom.toggleClass(this.getContainer(), SearchView.ACTIONS_RIGHT_CLASS_NAME, actionsPosition === 'right');
		dom.toggleClass(this.getContainer(), SearchView.WIDE_CLASS_NAME, this.size.width >= SearchView.WIDE_VIEW_SIZE);
892

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

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

898 899 900 901
		const messagesSize = this.messagesElement.style.display === 'none' ?
			0 :
			dom.getTotalHeight(this.messagesElement);

902 903
		const searchResultContainerSize = this.size.height -
			messagesSize -
904
			dom.getTotalHeight(this.searchWidgetsContainerElement);
905

906
		this.resultsElement.style.height = searchResultContainerSize + 'px';
E
Erich Gamma 已提交
907

908
		this.tree.layout(searchResultContainerSize, this.size.width);
E
Erich Gamma 已提交
909 910
	}

R
Rob Lourens 已提交
911
	layout(dimension: dom.Dimension): void {
E
Erich Gamma 已提交
912
		this.size = dimension;
B
Benjamin Pasero 已提交
913
		this.reLayout();
E
Erich Gamma 已提交
914 915
	}

R
Rob Lourens 已提交
916
	getControl() {
E
Erich Gamma 已提交
917 918 919
		return this.tree;
	}

R
Rob Lourens 已提交
920
	isSearchSubmitted(): boolean {
S
Sandeep Somavarapu 已提交
921 922 923
		return this.searchSubmitted;
	}

R
Rob Lourens 已提交
924
	isSearching(): boolean {
925 926 927
		return this.searching;
	}

R
Rob Lourens 已提交
928
	allSearchFieldsClear(): boolean {
929 930 931 932 933 934
		return this.searchWidget.getReplaceValue() === '' &&
			this.searchWidget.searchInput.getValue() === '' &&
			this.searchIncludePattern.getValue() === '' &&
			this.searchExcludePattern.getValue() === '';
	}

R
Rob Lourens 已提交
935
	hasSearchResults(): boolean {
S
Sandeep Somavarapu 已提交
936 937 938
		return !this.viewModel.searchResult.isEmpty();
	}

R
Rob Lourens 已提交
939
	clearSearchResults(): void {
940
		this.viewModel.searchResult.clear();
E
Erich Gamma 已提交
941
		this.showEmptyStage();
942
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
943
			this.showSearchWithoutFolderMessage();
944
		}
945
		this.searchWidget.clear();
946 947
		this.searchIncludePattern.setValue('');
		this.searchExcludePattern.setValue('');
S
Sandeep Somavarapu 已提交
948
		this.viewModel.cancelSearch();
949
		this.updateActions();
E
Erich Gamma 已提交
950 951
	}

R
Rob Lourens 已提交
952
	cancelSearch(): boolean {
S
Sandeep Somavarapu 已提交
953
		if (this.viewModel.cancelSearch()) {
954
			this.searchWidget.focus();
955 956 957 958 959
			return true;
		}
		return false;
	}

960
	private selectTreeIfNotSelected(): void {
R
Rob Lourens 已提交
961
		if (this.tree.getNode(null)) {
962
			this.tree.domFocus();
963
			const selection = this.tree.getSelection();
E
Erich Gamma 已提交
964 965 966 967 968 969
			if (selection.length === 0) {
				this.tree.focusNext();
			}
		}
	}

R
Rob Lourens 已提交
970
	private getSearchTextFromEditor(allowUnselectedWord: boolean): string {
B
Benjamin Pasero 已提交
971
		if (!this.editorService.activeEditor) {
E
Erich Gamma 已提交
972 973 974
			return null;
		}

R
Rob Lourens 已提交
975 976 977 978
		if (dom.isAncestor(document.activeElement, this.getContainer())) {
			return null;
		}

979 980 981 982
		let activeTextEditorWidget = this.editorService.activeTextEditorWidget;
		if (isDiffEditor(activeTextEditorWidget)) {
			if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) {
				activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor();
983
			} else {
984
				activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
985 986 987
			}
		}

988
		if (!isCodeEditor(activeTextEditorWidget)) {
E
Erich Gamma 已提交
989 990 991
			return null;
		}

992
		const range = activeTextEditorWidget.getSelection();
R
Rob Lourens 已提交
993 994 995 996
		if (!range) {
			return null;
		}

R
Rob Lourens 已提交
997
		if (range.isEmpty() && !this.searchWidget.searchInput.getValue() && allowUnselectedWord) {
998
			const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition());
R
Rob Lourens 已提交
999 1000 1001 1002 1003
			if (wordAtPosition) {
				return wordAtPosition.word;
			}
		}

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
		if (!range.isEmpty()) {
			let searchText = '';
			for (let i = range.startLineNumber; i <= range.endLineNumber; i++) {
				let lineText = activeTextEditorWidget.getModel().getLineContent(i);
				if (i === range.endLineNumber) {
					lineText = lineText.substring(0, range.endColumn - 1);
				}

				if (i === range.startLineNumber) {
					lineText = lineText.substring(range.startColumn - 1);
				}

				if (i !== range.startLineNumber) {
					lineText = '\n' + lineText;
				}

				searchText += lineText;
			}

1023
			return searchText;
E
Erich Gamma 已提交
1024
		}
1025

E
Erich Gamma 已提交
1026 1027 1028 1029 1030 1031 1032
		return null;
	}

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

R
Rob Lourens 已提交
1033
	toggleCaseSensitive(): void {
S
Sandeep Somavarapu 已提交
1034
		this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive());
1035
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
1036 1037
	}

R
Rob Lourens 已提交
1038
	toggleWholeWords(): void {
S
Sandeep Somavarapu 已提交
1039
		this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords());
1040
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
1041 1042
	}

R
Rob Lourens 已提交
1043
	toggleRegex(): void {
S
Sandeep Somavarapu 已提交
1044
		this.searchWidget.searchInput.setRegex(!this.searchWidget.searchInput.getRegex());
1045
		this.onQueryChanged(true);
S
Sandeep Somavarapu 已提交
1046 1047
	}

R
Rob Lourens 已提交
1048
	toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean, reverse?: boolean): void {
1049
		const cls = 'more';
E
Erich Gamma 已提交
1050
		show = typeof show === 'undefined' ? !dom.hasClass(this.queryDetails, cls) : Boolean(show);
B
Benjamin Pasero 已提交
1051
		this.viewletState['query.queryDetailsExpanded'] = show;
E
Erich Gamma 已提交
1052 1053 1054
		skipLayout = Boolean(skipLayout);

		if (show) {
1055
			this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true');
E
Erich Gamma 已提交
1056
			dom.addClass(this.queryDetails, cls);
1057
			if (moveFocus) {
1058 1059 1060 1061 1062 1063 1064
				if (reverse) {
					this.inputPatternExcludes.focus();
					this.inputPatternExcludes.select();
				} else {
					this.inputPatternIncludes.focus();
					this.inputPatternIncludes.select();
				}
1065
			}
E
Erich Gamma 已提交
1066
		} else {
1067
			this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'false');
E
Erich Gamma 已提交
1068
			dom.removeClass(this.queryDetails, cls);
1069
			if (moveFocus) {
1070
				this.searchWidget.focus();
1071
			}
E
Erich Gamma 已提交
1072
		}
B
Benjamin Pasero 已提交
1073

E
Erich Gamma 已提交
1074 1075 1076 1077 1078
		if (!skipLayout && this.size) {
			this.layout(this.size);
		}
	}

R
Rob Lourens 已提交
1079
	searchInFolders(resources: URI[], pathToRelative: (from: string, to: string) => string): void {
1080
		const folderPaths: string[] = [];
1081
		const workspace = this.contextService.getWorkspace();
1082 1083 1084

		if (resources) {
			resources.forEach(resource => {
1085
				let folderPath: string | undefined;
1086 1087
				if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
					// Show relative path from the root for single-root mode
B
Benjamin Pasero 已提交
1088
					folderPath = extpath.normalize(pathToRelative(workspace.folders[0].uri.fsPath, resource.fsPath));
1089 1090 1091 1092 1093 1094
					if (folderPath && folderPath !== '.') {
						folderPath = './' + folderPath;
					}
				} else {
					const owningFolder = this.contextService.getWorkspaceFolder(resource);
					if (owningFolder) {
1095
						const owningRootName = owningFolder.name;
1096 1097

						// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
1098
						const isUniqueFolder = workspace.folders.filter(folder => folder.name === owningRootName).length === 1;
1099
						if (isUniqueFolder) {
B
Benjamin Pasero 已提交
1100
							const relativePath = extpath.normalize(pathToRelative(owningFolder.uri.fsPath, resource.fsPath));
1101 1102 1103 1104 1105
							if (relativePath === '.') {
								folderPath = `./${owningFolder.name}`;
							} else {
								folderPath = `./${owningFolder.name}/${relativePath}`;
							}
1106 1107 1108
						} else {
							folderPath = resource.fsPath;
						}
1109 1110
					}
				}
1111 1112 1113 1114 1115

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

1118
		if (!folderPaths.length || folderPaths.some(folderPath => folderPath === '.')) {
S
Sandeep Somavarapu 已提交
1119 1120 1121 1122 1123
			this.inputPatternIncludes.setValue('');
			this.searchWidget.focus();
			return;
		}

1124
		// Show 'files to include' box
E
Erich Gamma 已提交
1125
		if (!this.showsFileTypes()) {
1126
			this.toggleQueryDetails(true, true);
E
Erich Gamma 已提交
1127
		}
B
Benjamin Pasero 已提交
1128

1129
		this.inputPatternIncludes.setValue(folderPaths.join(', '));
B
Benjamin Pasero 已提交
1130
		this.searchWidget.focus(false);
E
Erich Gamma 已提交
1131 1132
	}

R
Rob Lourens 已提交
1133
	onQueryChanged(preserveFocus?: boolean): void {
1134 1135 1136 1137
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1138 1139 1140
		const excludePatternText = this.inputPatternExcludes.getValue().trim();
		const includePatternText = this.inputPatternIncludes.getValue().trim();
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
E
Erich Gamma 已提交
1141 1142 1143 1144 1145

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

1146
		const content: IPatternInfo = {
E
Erich Gamma 已提交
1147 1148 1149
			pattern: contentPattern,
			isRegExp: isRegex,
			isCaseSensitive: isCaseSensitive,
R
Rob Lourens 已提交
1150
			isWordMatch: isWholeWords
E
Erich Gamma 已提交
1151 1152
		};

1153 1154
		const excludePattern = this.inputPatternExcludes.getValue();
		const includePattern = this.inputPatternIncludes.getValue();
E
Erich Gamma 已提交
1155

R
Rob Lourens 已提交
1156 1157 1158
		// 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 已提交
1159
		const charsPerLine = content.isRegExp ? 10000 :
1160
			250;
R
Rob Lourens 已提交
1161

1162
		const options: ITextQueryBuilderOptions = {
R
Rob Lourens 已提交
1163
			_reason: 'searchView',
1164
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
1165
			maxResults: SearchView.MAX_TEXT_RESULTS,
R
Rob Lourens 已提交
1166 1167
			disregardIgnoreFiles: !useExcludesAndIgnoreFiles || undefined,
			disregardExcludeSettings: !useExcludesAndIgnoreFiles || undefined,
R
Rob Lourens 已提交
1168
			excludePattern,
1169 1170
			includePattern,
			previewOptions: {
R
Rob Lourens 已提交
1171 1172
				matchLines: 1,
				charsPerLine
R
Rob Lourens 已提交
1173 1174
			},
			isSmartCase: this.configurationService.getValue<ISearchConfiguration>().search.smartCase
E
Erich Gamma 已提交
1175
		};
S
Sandeep Somavarapu 已提交
1176
		const folderResources = this.contextService.getWorkspace().folders;
1177 1178 1179 1180 1181 1182

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

1183
		let query: ITextQuery;
1184
		try {
1185
			query = this.queryBuilder.text(content, folderResources.map(folder => folder.uri), options);
1186 1187
		} catch (err) {
			onQueryValidationError(err);
1188
			return;
1189
		}
1190

1191
		this.validateQuery(query).then(() => {
1192
			this.onQueryTriggered(query, options, excludePatternText, includePatternText);
1193

1194 1195 1196
			if (!preserveFocus) {
				this.searchWidget.focus(false); // focus back to input field
			}
1197
		}, onQueryValidationError);
1198 1199
	}

J
Johannes Rieken 已提交
1200
	private validateQuery(query: ITextQuery): Promise<void> {
1201 1202 1203 1204 1205 1206
		// Validate folderQueries
		const folderQueriesExistP =
			query.folderQueries.map(fq => {
				return this.fileService.existsFile(fq.folder);
			});

1207
		return Promise.resolve(folderQueriesExistP).then(existResults => {
R
Rob Lourens 已提交
1208 1209
			// If no folders exist, show an error message about the first one
			const existingFolderQueries = query.folderQueries.filter((folderQuery, i) => existResults[i]);
1210
			if (!query.folderQueries.length || existingFolderQueries.length) {
R
Rob Lourens 已提交
1211 1212 1213
				query.folderQueries = existingFolderQueries;
			} else {
				const nonExistantPath = query.folderQueries[0].folder.fsPath;
1214
				const searchPathNotFoundError = nls.localize('searchPathNotFoundError', "Search path not found: {0}", nonExistantPath);
1215
				return Promise.reject(new Error(searchPathNotFoundError));
1216 1217 1218 1219
			}

			return undefined;
		});
E
Erich Gamma 已提交
1220 1221
	}

1222
	private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): void {
R
Rob Lourens 已提交
1223
		this.searchWidget.searchInput.onSearchSubmit();
1224
		this.inputPatternExcludes.onSearchSubmit();
1225 1226
		this.inputPatternIncludes.onSearchSubmit();

S
Sandeep Somavarapu 已提交
1227 1228
		this.viewModel.cancelSearch();

1229 1230
		this.currentSearchQ = this.currentSearchQ
			.then(() => this.doSearch(query, options, excludePatternText, includePatternText))
J
Johannes Rieken 已提交
1231
			.then(() => undefined, () => undefined);
1232 1233 1234
	}

	private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable<void> {
1235
		const progressRunner = this.progressService.show(/*infinite=*/true);
E
Erich Gamma 已提交
1236

1237
		this.searchWidget.searchInput.clearMessage();
1238 1239 1240
		this.searching = true;
		setTimeout(() => {
			if (this.searching) {
1241 1242
				this.updateActions();
				this.updateTitleArea();
1243 1244
			}
		}, 2000);
E
Erich Gamma 已提交
1245 1246
		this.showEmptyStage();

1247
		const onComplete = (completed?: ISearchComplete) => {
1248
			this.searching = false;
1249 1250

			// Complete up to 100% as needed
1251
			progressRunner.done();
E
Erich Gamma 已提交
1252

1253
			// Do final render, then expand if just 1 file with less than 50 matches
R
Rob Lourens 已提交
1254
			this.onSearchResultsChanged();
1255 1256 1257

			const collapseResults = this.configurationService.getValue<ISearchConfigurationProperties>('search').collapseResults;
			if (collapseResults !== 'alwaysCollapse' && this.viewModel.searchResult.matches().length === 1) {
R
Rob Lourens 已提交
1258 1259 1260
				const onlyMatch = this.viewModel.searchResult.matches()[0];
				if (onlyMatch.count() < 50) {
					this.tree.expand(onlyMatch);
1261
				}
R
Rob Lourens 已提交
1262
			}
1263

R
Rob Lourens 已提交
1264
			this.viewModel.replaceString = this.searchWidget.getReplaceValue();
E
Erich Gamma 已提交
1265

R
Rob Lourens 已提交
1266 1267 1268
			this.searchSubmitted = true;
			this.updateActions();
			this.updateTitleArea();
1269
			const hasResults = !this.viewModel.searchResult.isEmpty();
E
Erich Gamma 已提交
1270

R
Rob Lourens 已提交
1271 1272 1273 1274 1275 1276
			if (completed && completed.limitHit) {
				this.searchWidget.searchInput.showMessage({
					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
				});
			}
E
Erich Gamma 已提交
1277

R
Rob Lourens 已提交
1278
			if (!hasResults) {
1279 1280
				const hasExcludes = !!excludePatternText;
				const hasIncludes = !!includePatternText;
R
Rob Lourens 已提交
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
				let message: string;

				if (!completed) {
					message = nls.localize('searchCanceled', "Search was canceled before any results could be found - ");
				} else if (hasIncludes && hasExcludes) {
					message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText);
				} else if (hasIncludes) {
					message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText);
				} else if (hasExcludes) {
					message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText);
				} else {
R
Rob Lourens 已提交
1292
					message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and check your gitignore files - ");
R
Rob Lourens 已提交
1293
				}
1294

R
Rob Lourens 已提交
1295 1296
				// Indicate as status to ARIA
				aria.status(message);
E
Erich Gamma 已提交
1297

R
Rob Lourens 已提交
1298
				dom.hide(this.resultsElement);
E
Erich Gamma 已提交
1299

R
Rob Lourens 已提交
1300 1301
				const messageEl = this.clearMessage();
				const p = dom.append(messageEl, $('p', undefined, message));
E
Erich Gamma 已提交
1302

R
Rob Lourens 已提交
1303 1304 1305 1306 1307 1308 1309 1310 1311 1312
				if (!completed) {
					const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again")));
					this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
						dom.EventHelper.stop(e, false);
						this.onQueryChanged();
					}));
				} else if (hasIncludes || hasExcludes) {
					const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files")));
					this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
						dom.EventHelper.stop(e, false);
1313

R
Rob Lourens 已提交
1314 1315
						this.inputPatternExcludes.setValue('');
						this.inputPatternIncludes.setValue('');
1316

R
Rob Lourens 已提交
1317 1318
						this.onQueryChanged();
					}));
1319
				} else {
R
Rob Lourens 已提交
1320 1321 1322 1323 1324 1325
					const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings")));
					this.addClickEvents(openSettingsLink, this.onOpenSettings);
				}

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

R
Rob Lourens 已提交
1327 1328
					const learnMoreLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.learnMore', "Learn More")));
					this.addClickEvents(learnMoreLink, this.onLearnMore);
1329
				}
R
Rob Lourens 已提交
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339

				if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
					this.showSearchWithoutFolderMessage();
				}
			} else {
				this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights

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

1342
		const onError = (e: any) => {
1343
			if (errors.isPromiseCanceledError(e)) {
1344
				return onComplete(null);
1345
			} else {
1346
				this.searching = false;
1347 1348
				this.updateActions();
				this.updateTitleArea();
1349
				progressRunner.done();
1350
				this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR });
1351
				this.viewModel.searchResult.clear();
1352

1353
				if (e.code === SearchErrorCode.regexParseError && !this.configurationService.getValue('search.usePCRE2')) {
1354
					this.showPcre2Hint();
1355
				}
1356 1357

				return Promise.resolve();
E
Erich Gamma 已提交
1358 1359 1360 1361 1362 1363
			}
		};

		let visibleMatches = 0;

		// Handle UI updates in an interval to show frequent progress and results
1364
		const uiRefreshHandle: any = setInterval(() => {
1365
			if (!this.searching) {
E
Erich Gamma 已提交
1366 1367 1368 1369 1370
				window.clearInterval(uiRefreshHandle);
				return;
			}

			// Search result tree update
1371 1372 1373
			const fileCount = this.viewModel.searchResult.fileCount();
			if (visibleMatches !== fileCount) {
				visibleMatches = fileCount;
R
Rob Lourens 已提交
1374
				this.refreshAndUpdateCount();
1375
			}
1376
			if (fileCount > 0) {
S
Sandeep Somavarapu 已提交
1377
				this.updateActions();
E
Erich Gamma 已提交
1378
			}
1379
		}, 100);
E
Erich Gamma 已提交
1380

S
Sandeep Somavarapu 已提交
1381
		this.searchWidget.setReplaceAllActionState(false);
1382

1383
		return this.viewModel.search(query)
1384
			.then(onComplete, onError);
E
Erich Gamma 已提交
1385 1386
	}

1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403
	private showPcre2Hint(): void {
		if (!this.globalMemento['disablePcre2Hint']) {
			// If the regex parsed in JS but not rg, it likely uses features that are supported in JS and PCRE2 but not Rust
			this.notificationService.prompt(Severity.Info, nls.localize('rgRegexError', "You can enable \"search.usePCRE2\" to enable some extra regex features like lookbehind and backreferences."), [
				{
					label: nls.localize('neverAgain', "Don't Show Again"),
					run: () => this.globalMemento['disablePcre2Hint'] = true,
					isSecondary: true
				},
				{
					label: nls.localize('otherEncodingWarning.openSettingsLabel', "Open Settings"),
					run: () => this.openSettings('search.usePCRE2')
				}
			]);
		}
	}

1404 1405
	private addClickEvents = (element: HTMLElement, handler: (event: any) => void): void => {
		this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.CLICK, handler));
1406
		this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.KEY_DOWN, e => {
1407
			const event = new StandardKeyboardEvent(e);
1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425
			let eventHandled = true;

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

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

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

1426 1427 1428
		this.openSettings('.exclude');
	}

J
Johannes Rieken 已提交
1429
	private openSettings(query: string): Promise<IEditor> {
1430 1431
		const options: ISettingsEditorOptions = { query };
		return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ?
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441
			this.preferencesService.openWorkspaceSettings(undefined, options) :
			this.preferencesService.openGlobalSettings(undefined, options);
	}

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

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

1442
	private updateSearchResultCount(disregardExcludesAndIgnores?: boolean): void {
1443
		const fileCount = this.viewModel.searchResult.fileCount();
1444 1445
		this.hasSearchResultsKey.set(fileCount > 0);

1446
		const msgWasHidden = this.messagesElement.style.display === 'none';
1447
		if (fileCount > 0) {
1448
			const messageEl = this.clearMessage();
1449 1450 1451 1452 1453 1454
			let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount);
			if (disregardExcludesAndIgnores) {
				resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled");
			}

			dom.append(messageEl, $('p', undefined, resultMsg));
R
Rob Lourens 已提交
1455
			this.reLayout();
1456
		} else if (!msgWasHidden) {
1457
			dom.hide(this.messagesElement);
1458 1459 1460
		}
	}

1461 1462 1463 1464 1465 1466 1467
	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);
1468
		} else {
1469
			return nls.localize('search.files.results', "{0} results in {1} files", resultCount, fileCount);
1470 1471 1472
		}
	}

1473 1474 1475 1476
	private showSearchWithoutFolderMessage(): void {
		this.searchWithoutFolderMessageElement = this.clearMessage();

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

		const openFolderLink = dom.append(textEl,
1480
			$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
1481 1482 1483 1484 1485 1486

		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);
1487
			this.actionRunner.run(action).then(() => {
1488 1489 1490 1491
				action.dispose();
			}, err => {
				action.dispose();
				errors.onUnexpectedError(err);
1492
			});
1493
		}));
1494 1495
	}

E
Erich Gamma 已提交
1496
	private showEmptyStage(): void {
1497

E
Erich Gamma 已提交
1498
		// disable 'result'-actions
S
Sandeep Somavarapu 已提交
1499 1500
		this.searchSubmitted = false;
		this.updateActions();
E
Erich Gamma 已提交
1501 1502

		// clean up ui
S
Sandeep Somavarapu 已提交
1503
		// this.replaceService.disposeAllReplacePreviews();
1504
		dom.hide(this.messagesElement);
1505
		dom.show(this.resultsElement);
S
Sandeep Somavarapu 已提交
1506
		this.currentSelectedFileMatch = null;
E
Erich Gamma 已提交
1507 1508
	}

J
Johannes Rieken 已提交
1509
	private onFocus(lineMatch: any, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
E
Erich Gamma 已提交
1510
		if (!(lineMatch instanceof Match)) {
1511
			this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
1512
			return Promise.resolve(true);
E
Erich Gamma 已提交
1513 1514
		}

1515 1516
		const useReplacePreview = this.configurationService.getValue<ISearchConfiguration>().search.useReplacePreview;
		return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ?
R
Rob Lourens 已提交
1517 1518
			this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) :
			this.open(lineMatch, preserveFocus, sideBySide, pinned);
1519 1520
	}

R
Rob Lourens 已提交
1521
	open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
R
Rob Lourens 已提交
1522 1523
		const selection = this.getSelectionFrom(element);
		const resource = element instanceof Match ? element.parent().resource() : (<FileMatch>element).resource();
E
Erich Gamma 已提交
1524
		return this.editorService.openEditor({
1525
			resource: resource,
E
Erich Gamma 已提交
1526
			options: {
1527 1528
				preserveFocus,
				pinned,
S
Sandeep Somavarapu 已提交
1529
				selection,
1530
				revealIfVisible: true
E
Erich Gamma 已提交
1531
			}
B
Benjamin Pasero 已提交
1532
		}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => {
S
Sandeep Somavarapu 已提交
1533
			if (editor && element instanceof Match && preserveFocus) {
1534 1535 1536 1537
				this.viewModel.searchResult.rangeHighlightDecorations.highlightRange(
					(<ICodeEditor>editor.getControl()).getModel(),
					element.range()
				);
S
Sandeep Somavarapu 已提交
1538
			} else {
1539
				this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
S
Sandeep Somavarapu 已提交
1540
			}
R
Rob Lourens 已提交
1541

R
Rob Lourens 已提交
1542 1543 1544
			if (editor) {
				return this.editorGroupsService.activateGroup(editor.group);
			} else {
1545
				return Promise.resolve(null);
R
Rob Lourens 已提交
1546
			}
S
Sandeep Somavarapu 已提交
1547
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1548 1549
	}

1550
	private getSelectionFrom(element: FileMatchOrMatch): any {
1551
		let match: Match | null = null;
1552
		if (element instanceof Match) {
J
Johannes Rieken 已提交
1553
			match = element;
1554 1555
		}
		if (element instanceof FileMatch && element.count() > 0) {
J
Johannes Rieken 已提交
1556
			match = element.matches()[element.matches().length - 1];
1557 1558
		}
		if (match) {
1559
			const range = match.range();
S
Sandeep Somavarapu 已提交
1560
			if (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) {
1561
				const replaceString = match.replaceString;
1562 1563
				return {
					startLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1564
					startColumn: range.startColumn,
1565
					endLineNumber: range.startLineNumber,
S
Sandeep Somavarapu 已提交
1566
					endColumn: range.startColumn + replaceString.length
1567 1568 1569 1570
				};
			}
			return range;
		}
R
Rob Lourens 已提交
1571
		return undefined;
1572 1573
	}

B
Benjamin Pasero 已提交
1574
	private onUntitledDidChangeDirty(resource: URI): void {
E
Erich Gamma 已提交
1575 1576 1577 1578
		if (!this.viewModel) {
			return;
		}

1579
		// remove search results from this resource as it got disposed
B
Benjamin Pasero 已提交
1580
		if (!this.untitledEditorService.isDirty(resource)) {
1581
			const matches = this.viewModel.searchResult.matches();
1582
			for (let i = 0, len = matches.length; i < len; i++) {
B
Benjamin Pasero 已提交
1583
				if (resource.toString() === matches[i].resource().toString()) {
1584 1585
					this.viewModel.searchResult.remove(matches[i]);
				}
E
Erich Gamma 已提交
1586 1587 1588 1589 1590
			}
		}
	}

	private onFilesChanged(e: FileChangesEvent): void {
B
Benjamin Pasero 已提交
1591
		if (!this.viewModel || !e.gotDeleted()) {
E
Erich Gamma 已提交
1592 1593 1594
			return;
		}

1595
		const matches = this.viewModel.searchResult.matches();
E
Erich Gamma 已提交
1596 1597 1598

		for (let i = 0, len = matches.length; i < len; i++) {
			if (e.contains(matches[i].resource(), FileChangeType.DELETED)) {
1599
				this.viewModel.searchResult.remove(matches[i]);
E
Erich Gamma 已提交
1600 1601 1602 1603
			}
		}
	}

R
Rob Lourens 已提交
1604
	getActions(): IAction[] {
1605 1606 1607 1608 1609 1610
		return [
			this.searching ?
				this.cancelAction :
				this.refreshAction,
			...this.actions
		];
1611 1612
	}

1613
	private clearHistory(): void {
1614 1615 1616 1617 1618
		this.searchWidget.clearHistory();
		this.inputPatternExcludes.clearHistory();
		this.inputPatternIncludes.clearHistory();
	}

B
Benjamin Pasero 已提交
1619
	protected saveState(): void {
A
Amy Qiu 已提交
1620 1621 1622 1623
		const isRegex = this.searchWidget.searchInput.getRegex();
		const isWholeWords = this.searchWidget.searchInput.getWholeWords();
		const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive();
		const contentPattern = this.searchWidget.searchInput.getValue();
1624
		const patternExcludes = this.inputPatternExcludes.getValue().trim();
A
Amy Qiu 已提交
1625
		const patternIncludes = this.inputPatternIncludes.getValue().trim();
1626
		const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
A
Amy Qiu 已提交
1627

B
Benjamin Pasero 已提交
1628 1629 1630 1631 1632 1633 1634
		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 已提交
1635

B
Benjamin Pasero 已提交
1636 1637
		const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
		this.viewletState['view.showReplace'] = isReplaceShown;
1638
		this.viewletState['query.replaceText'] = isReplaceShown && this.searchWidget.getReplaceValue();
B
Benjamin Pasero 已提交
1639

1640 1641
		const history: ISearchHistoryValues = Object.create(null);

1642
		const searchHistory = this.searchWidget.getSearchHistory();
1643 1644 1645 1646
		if (searchHistory && searchHistory.length) {
			history.search = searchHistory;
		}

1647
		const replaceHistory = this.searchWidget.getReplaceHistory();
1648 1649 1650 1651
		if (replaceHistory && replaceHistory.length) {
			history.replace = replaceHistory;
		}

1652
		const patternExcludesHistory = this.inputPatternExcludes.getHistory();
1653 1654 1655 1656
		if (patternExcludesHistory && patternExcludesHistory.length) {
			history.exclude = patternExcludesHistory;
		}

1657
		const patternIncludesHistory = this.inputPatternIncludes.getHistory();
1658 1659 1660
		if (patternIncludesHistory && patternIncludesHistory.length) {
			history.include = patternIncludesHistory;
		}
1661

1662
		this.searchHistoryService.save(history);
B
Benjamin Pasero 已提交
1663 1664

		super.saveState();
A
Amy Qiu 已提交
1665 1666
	}

R
Rob Lourens 已提交
1667
	dispose(): void {
E
Erich Gamma 已提交
1668 1669 1670 1671
		this.isDisposed = true;

		super.dispose();
	}
1672 1673 1674
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
B
Benjamin Pasero 已提交
1675
	const matchHighlightColor = theme.getColor(editorFindMatchHighlight);
1676
	if (matchHighlightColor) {
I
isidor 已提交
1677
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`);
B
Benjamin Pasero 已提交
1678 1679 1680 1681
	}

	const diffInsertedColor = theme.getColor(diffInserted);
	if (diffInsertedColor) {
I
isidor 已提交
1682
		collector.addRule(`.monaco-workbench .search-view .replaceMatch { background-color: ${diffInsertedColor}; }`);
B
Benjamin Pasero 已提交
1683 1684 1685 1686
	}

	const diffRemovedColor = theme.getColor(diffRemoved);
	if (diffRemovedColor) {
I
isidor 已提交
1687
		collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { background-color: ${diffRemovedColor}; }`);
B
Benjamin Pasero 已提交
1688 1689 1690 1691
	}

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

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

1700 1701
	const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
	if (findMatchHighlightBorder) {
1702
		collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findMatchHighlightBorder}; }`);
1703
	}
1704 1705 1706

	const outlineSelectionColor = theme.getColor(listActiveSelectionForeground);
	if (outlineSelectionColor) {
1707
		collector.addRule(`.monaco-workbench .search-view .monaco-list.element-focused .monaco-list-row.focused.selected:not(.highlighted) .action-label:focus { outline-color: ${outlineSelectionColor} }`);
1708
	}
1709
});