search.ts 12.4 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 7
import { mapArrayOrNot } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
8 9 10
import { Event } from 'vs/base/common/event';
import * as glob from 'vs/base/common/glob';
import { IDisposable } from 'vs/base/common/lifecycle';
11
import * as objects from 'vs/base/common/objects';
12
import * as paths from 'vs/base/common/paths';
13
import { getNLines } from 'vs/base/common/strings';
14
import { URI, UriComponents } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
15 16
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
E
Erich Gamma 已提交
17

18
export const VIEW_ID = 'workbench.view.search';
E
Erich Gamma 已提交
19

20 21
export const ISearchHistoryService = createDecorator<ISearchHistoryService>('searchHistoryService');
export const ISearchService = createDecorator<ISearchService>('searchService');
B
Benjamin Pasero 已提交
22

E
Erich Gamma 已提交
23 24 25 26
/**
 * A service that enables to search for files or with in files.
 */
export interface ISearchService {
27
	_serviceBrand: any;
28 29
	textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise<ISearchComplete>;
	fileSearch(query: IFileQuery, token?: CancellationToken): Promise<ISearchComplete>;
J
Johannes Rieken 已提交
30
	clearCache(cacheKey: string): Promise<void>;
31
	registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable;
32 33
}

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
export interface ISearchHistoryValues {
	search?: string[];
	replace?: string[];
	include?: string[];
	exclude?: string[];
}

export interface ISearchHistoryService {
	_serviceBrand: any;
	onDidClearHistory: Event<void>;
	clearHistory(): void;
	load(): ISearchHistoryValues;
	save(history: ISearchHistoryValues): void;
}

49 50 51
/**
 * TODO@roblou - split text from file search entirely, or share code in a more natural way.
 */
52
export const enum SearchProviderType {
53 54 55 56 57
	file,
	fileIndex,
	text
}

58
export interface ISearchResultProvider {
R
Rob Lourens 已提交
59 60
	textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise<ISearchComplete | undefined>;
	fileSearch(query: IFileQuery, token?: CancellationToken): Promise<ISearchComplete | undefined>;
J
Johannes Rieken 已提交
61
	clearCache(cacheKey: string): Promise<void>;
E
Erich Gamma 已提交
62 63
}

64
export interface IFolderQuery<U extends UriComponents=URI> {
65
	folder: U;
66 67
	excludePattern?: glob.IExpression;
	includePattern?: glob.IExpression;
R
Rob Lourens 已提交
68
	fileEncoding?: string;
69
	disregardIgnoreFiles?: boolean;
70
	disregardGlobalIgnoreFiles?: boolean;
71
	ignoreSymlinks?: boolean;
R
Rob Lourens 已提交
72 73
}

74
export interface ICommonQueryProps<U extends UriComponents> {
R
Rob Lourens 已提交
75 76 77
	/** For telemetry - indicates what is triggering the source */
	_reason?: string;

R
Rob Lourens 已提交
78
	folderQueries: IFolderQuery<U>[];
79 80
	includePattern?: glob.IExpression;
	excludePattern?: glob.IExpression;
81
	extraFileResources?: U[];
82

E
Erich Gamma 已提交
83
	maxResults?: number;
84 85 86 87 88 89 90 91 92 93
	usingSearchPaths?: boolean;
}

export interface IFileQueryProps<U extends UriComponents> extends ICommonQueryProps<U> {
	type: QueryType.File;
	filePattern?: string;

	// TODO: Remove this!
	disregardExcludeSettings?: boolean;

94 95 96 97 98
	/**
	 * If true no results will be returned. Instead `limitHit` will indicate if at least one result exists or not.
	 * Currently does not work with queries including a 'siblings clause'.
	 */
	exists?: boolean;
99 100
	sortByScore?: boolean;
	cacheKey?: string;
E
Erich Gamma 已提交
101 102
}

103 104
export interface ITextQueryProps<U extends UriComponents> extends ICommonQueryProps<U> {
	type: QueryType.Text;
105
	contentPattern: IPatternInfo;
106 107 108

	previewOptions?: ITextSearchPreviewOptions;
	maxFileSize?: number;
R
Rob Lourens 已提交
109
	usePCRE2?: boolean;
110 111
	afterContext?: number;
	beforeContext?: number;
112 113

	userDisabledExcludesAndIgnoreFiles?: boolean;
E
Erich Gamma 已提交
114 115
}

116
export type IFileQuery = IFileQueryProps<URI>;
117
export type IRawFileQuery = IFileQueryProps<UriComponents>;
118
export type ITextQuery = ITextQueryProps<URI>;
119 120 121 122
export type IRawTextQuery = ITextQueryProps<UriComponents>;

export type IRawQuery = IRawTextQuery | IRawFileQuery;
export type ISearchQuery = ITextQuery | IFileQuery;
123

124
export const enum QueryType {
E
Erich Gamma 已提交
125 126 127
	File = 1,
	Text = 2
}
R
Rob Lourens 已提交
128

K
kieferrm 已提交
129
/* __GDPR__FRAGMENT__
K
kieferrm 已提交
130 131
	"IPatternInfo" : {
		"pattern" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
K
kieferrm 已提交
132 133
		"isRegExp": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
		"isWordMatch": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
K
kieferrm 已提交
134
		"wordSeparators": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
135 136 137
		"isMultiline": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
		"isCaseSensitive": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
		"isSmartCase": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
K
kieferrm 已提交
138 139
	}
*/
E
Erich Gamma 已提交
140 141 142 143
export interface IPatternInfo {
	pattern: string;
	isRegExp?: boolean;
	isWordMatch?: boolean;
144
	wordSeparators?: string;
S
Sandeep Somavarapu 已提交
145
	isMultiline?: boolean;
E
Erich Gamma 已提交
146 147 148
	isCaseSensitive?: boolean;
}

R
Rob Lourens 已提交
149 150 151 152
export interface IExtendedExtensionSearchOptions {
	usePCRE2?: boolean;
}

153
export interface IFileMatch<U extends UriComponents = URI> {
R
Rob Lourens 已提交
154
	resource: U;
155
	results?: ITextSearchResult[];
E
Erich Gamma 已提交
156 157
}

158
export type IRawFileMatch2 = IFileMatch<UriComponents>;
159

160
export interface ITextSearchPreviewOptions {
R
Rob Lourens 已提交
161 162
	matchLines: number;
	charsPerLine: number;
163 164 165 166 167 168 169 170 171 172 173
}

export interface ISearchRange {
	readonly startLineNumber: number;
	readonly startColumn: number;
	readonly endLineNumber: number;
	readonly endColumn: number;
}

export interface ITextSearchResultPreview {
	text: string;
174
	matches: ISearchRange | ISearchRange[];
175 176
}

177 178
export interface ITextSearchMatch {
	uri?: URI;
179
	ranges: ISearchRange | ISearchRange[];
180
	preview: ITextSearchResultPreview;
E
Erich Gamma 已提交
181 182
}

183 184 185 186 187 188 189 190 191 192 193 194
export interface ITextSearchContext {
	uri?: URI;
	text: string;
	lineNumber: number;
}

export type ITextSearchResult = ITextSearchMatch | ITextSearchContext;

export function resultIsMatch(result: ITextSearchResult): result is ITextSearchMatch {
	return !!(<ITextSearchMatch>result).preview;
}

E
Erich Gamma 已提交
195 196 197
export interface IProgress {
	total?: number;
	worked?: number;
198 199 200
	message?: string;
}

R
Rob Lourens 已提交
201
export type ISearchProgressItem = IFileMatch | IProgress;
E
Erich Gamma 已提交
202

203
export interface ISearchCompleteStats {
E
Erich Gamma 已提交
204
	limitHit?: boolean;
205
	stats?: IFileSearchStats | ITextSearchStats;
206 207 208
}

export interface ISearchComplete extends ISearchCompleteStats {
E
Erich Gamma 已提交
209
	results: IFileMatch[];
C
chrmarti 已提交
210 211
}

212 213 214 215
export interface ITextSearchStats {
	type: 'textSearchProvider' | 'searchProcess';
}

216
export interface IFileSearchStats {
217
	fromCache: boolean;
218
	detailStats: ISearchEngineStats | ICachedSearchStats | IFileSearchProviderStats | IFileIndexProviderStats;
219

220
	resultCount: number;
R
Rob Lourens 已提交
221
	type: 'fileIndexProvider' | 'fileSearchProvider' | 'searchProcess';
222
	sortingTime?: number;
223 224
}

225 226 227 228
export interface ICachedSearchStats {
	cacheWasResolved: boolean;
	cacheLookupTime: number;
	cacheFilterTime: number;
229 230 231
	cacheEntryCount: number;
}

232 233
export interface ISearchEngineStats {
	fileWalkTime: number;
C
chrmarti 已提交
234 235
	directoriesWalked: number;
	filesWalked: number;
236
	cmdTime: number;
C
Christof Marti 已提交
237
	cmdResultCount?: number;
E
Erich Gamma 已提交
238 239
}

240 241 242 243 244 245 246 247 248 249 250 251 252
export interface IFileSearchProviderStats {
	providerTime: number;
	postProcessTime: number;
}

export interface IFileIndexProviderStats {
	providerTime: number;
	providerResultCount: number;
	fileWalkTime: number;
	directoriesWalked: number;
	filesWalked: number;
}

E
Erich Gamma 已提交
253
export class FileMatch implements IFileMatch {
R
Rob Lourens 已提交
254
	results: ITextSearchResult[] = [];
255
	constructor(public resource: URI) {
E
Erich Gamma 已提交
256 257 258 259
		// empty
	}
}

260
export class TextSearchMatch implements ITextSearchMatch {
261
	ranges: ISearchRange | ISearchRange[];
262 263
	preview: ITextSearchResultPreview;

264 265
	constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) {
		this.ranges = range;
266

267
		if (previewOptions && previewOptions.matchLines === 1 && !Array.isArray(range)) {
268
			// 1 line preview requested
269
			text = getNLines(text, previewOptions.matchLines);
R
Rob Lourens 已提交
270
			const leadingChars = Math.floor(previewOptions.charsPerLine / 5);
271
			const previewStart = Math.max(range.startColumn - leadingChars, 0);
272 273 274 275 276
			const previewText = text.substring(previewStart, previewOptions.charsPerLine + previewStart);

			const endColInPreview = (range.endLineNumber - range.startLineNumber + 1) <= previewOptions.matchLines ?
				Math.min(previewText.length, range.endColumn - previewStart) :  // if number of match lines will not be trimmed by previewOptions
				previewText.length; // if number of lines is trimmed
277 278

			this.preview = {
279
				text: previewText,
280
				matches: new OneLineRange(0, range.startColumn - previewStart, endColInPreview)
281 282
			};
		} else {
283 284 285
			const firstMatchLine = Array.isArray(range) ? range[0].startLineNumber : range.startLineNumber;

			// n line, no preview requested, or multiple matches in the preview
286
			this.preview = {
287
				text,
288
				matches: mapArrayOrNot(range, r => new SearchRange(r.startLineNumber - firstMatchLine, r.startColumn, r.endLineNumber - firstMatchLine, r.endColumn))
289 290 291 292 293
			};
		}
	}
}

294
export class SearchRange implements ISearchRange {
295 296 297 298 299
	startLineNumber: number;
	startColumn: number;
	endLineNumber: number;
	endColumn: number;

300 301
	constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number) {
		this.startLineNumber = startLineNumber;
302
		this.startColumn = startColumn;
303
		this.endLineNumber = endLineNumber;
304
		this.endColumn = endColumn;
E
Erich Gamma 已提交
305 306 307
	}
}

308 309 310 311 312 313
export class OneLineRange extends SearchRange {
	constructor(lineNumber: number, startColumn: number, endColumn: number) {
		super(lineNumber, startColumn, lineNumber, endColumn);
	}
}

314 315 316 317 318 319 320
export interface ISearchConfigurationProperties {
	exclude: glob.IExpression;
	useRipgrep: boolean;
	/**
	 * Use ignore file for file search.
	 */
	useIgnoreFiles: boolean;
P
pkoushik 已提交
321
	useGlobalIgnoreFiles: boolean;
322 323
	followSymlinks: boolean;
	smartCase: boolean;
324
	globalFindClipboard: boolean;
325
	location: 'sidebar' | 'panel';
326
	useReplacePreview: boolean;
327
	showLineNumbers: boolean;
R
Rob Lourens 已提交
328
	usePCRE2: boolean;
R
Rob Lourens 已提交
329
	actionsPosition: 'auto' | 'right';
330
	maintainFileSearchCache: boolean;
331
	collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand';
332 333
}

E
Erich Gamma 已提交
334
export interface ISearchConfiguration extends IFilesConfiguration {
335
	search: ISearchConfigurationProperties;
336 337 338
	editor: {
		wordSeparators: string;
	};
B
Benjamin Pasero 已提交
339 340
}

341
export function getExcludes(configuration: ISearchConfiguration, includeSearchExcludes = true): glob.IExpression | undefined {
B
Benjamin Pasero 已提交
342
	const fileExcludes = configuration && configuration.files && configuration.files.exclude;
343
	const searchExcludes = includeSearchExcludes && configuration && configuration.search && configuration.search.exclude;
B
Benjamin Pasero 已提交
344 345

	if (!fileExcludes && !searchExcludes) {
R
Rob Lourens 已提交
346
		return undefined;
B
Benjamin Pasero 已提交
347 348 349 350 351 352
	}

	if (!fileExcludes || !searchExcludes) {
		return fileExcludes || searchExcludes;
	}

353
	let allExcludes: glob.IExpression = Object.create(null);
S
Sandeep Somavarapu 已提交
354
	// clone the config as it could be frozen
J
Johannes Rieken 已提交
355 356
	allExcludes = objects.mixin(allExcludes, objects.deepClone(fileExcludes));
	allExcludes = objects.mixin(allExcludes, objects.deepClone(searchExcludes), true);
B
Benjamin Pasero 已提交
357 358

	return allExcludes;
359
}
360

361
export function pathIncludedInQuery(queryProps: ICommonQueryProps<URI>, fsPath: string): boolean {
362
	if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) {
363 364 365
		return false;
	}

366
	if (queryProps.includePattern && !glob.match(queryProps.includePattern, fsPath)) {
367 368 369 370
		return false;
	}

	// If searchPaths are being used, the extra file must be in a subfolder and match the pattern, if present
371 372
	if (queryProps.usingSearchPaths) {
		return !!queryProps.folderQueries && queryProps.folderQueries.every(fq => {
373 374 375 376 377 378 379 380 381 382 383
			const searchPath = fq.folder.fsPath;
			if (paths.isEqualOrParent(fsPath, searchPath)) {
				return !fq.includePattern || !!glob.match(fq.includePattern, fsPath);
			} else {
				return false;
			}
		});
	}

	return true;
}
R
Rob Lourens 已提交
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

export enum SearchErrorCode {
	unknownEncoding = 1,
	regexParseError,
	globParseError,
	invalidLiteral,
	rgProcessError,
	other
}

export class SearchError extends Error {
	constructor(message: string, readonly code?: SearchErrorCode) {
		super(message);
	}
}

export function deserializeSearchError(errorMsg: string): SearchError {
	try {
		const details = JSON.parse(errorMsg);
		return new SearchError(details.message, details.code);
	} catch (e) {
		return new SearchError(errorMsg, SearchErrorCode.other);
	}
}

export function serializeSearchError(searchError: SearchError): Error {
	const details = { message: searchError.message, code: searchError.code };
	return new Error(JSON.stringify(details));
}