search.ts 11.1 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 8
import { Event } from 'vs/base/common/event';
import * as glob from 'vs/base/common/glob';
import { IDisposable } from 'vs/base/common/lifecycle';
9
import * as objects from 'vs/base/common/objects';
10
import * as paths from 'vs/base/common/paths';
11
import { URI as uri, UriComponents } from 'vs/base/common/uri';
12
import { TPromise } from 'vs/base/common/winjs.base';
J
Johannes Rieken 已提交
13 14
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
15
import { CancellationToken } from 'vs/base/common/cancellation';
16
import { getNLines } from 'vs/base/common/strings';
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 30
	textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete>;
	fileSearch(query: IFileQuery, token?: CancellationToken): TPromise<ISearchComplete>;
	extendQuery(query: ITextQuery | IFileQuery): void;
31
	clearCache(cacheKey: string): TPromise<void>;
32
	registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable;
33 34
}

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
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;
}

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

59
export interface ISearchResultProvider {
60 61
	textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete>;
	fileSearch(query: IFileQuery, token?: CancellationToken): TPromise<ISearchComplete>;
62
	clearCache(cacheKey: string): TPromise<void>;
E
Erich Gamma 已提交
63 64
}

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

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

79 80 81
	folderQueries?: IFolderQuery<U>[];
	includePattern?: glob.IExpression;
	excludePattern?: glob.IExpression;
82
	extraFileResources?: U[];
83 84

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

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

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

96 97 98 99 100
	/**
	 * 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;
101 102
	sortByScore?: boolean;
	cacheKey?: string;
E
Erich Gamma 已提交
103 104
}

105 106
export interface ITextQueryProps<U extends UriComponents> extends ICommonQueryProps<U> {
	type: QueryType.Text;
E
Erich Gamma 已提交
107
	contentPattern?: IPatternInfo;
108 109 110

	previewOptions?: ITextSearchPreviewOptions;
	maxFileSize?: number;
R
Rob Lourens 已提交
111
	usePCRE2?: boolean;
E
Erich Gamma 已提交
112 113
}

114 115 116 117 118 119 120
export type IFileQuery = IFileQueryProps<uri>;
export type IRawFileQuery = IFileQueryProps<UriComponents>;
export type ITextQuery = ITextQueryProps<uri>;
export type IRawTextQuery = ITextQueryProps<UriComponents>;

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

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

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

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

152
export interface IFileMatch<U extends UriComponents = uri> {
153
	resource?: U;
154
	matches?: ITextSearchResult[];
E
Erich Gamma 已提交
155 156
}

157
export type IRawFileMatch2 = IFileMatch<UriComponents>;
158

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

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

export interface ITextSearchResultPreview {
	text: string;
	match: ISearchRange;
}

export interface ITextSearchResult {
	uri?: uri;
	range: ISearchRange;
	preview: ITextSearchResultPreview;
E
Erich Gamma 已提交
180 181 182 183 184
}

export interface IProgress {
	total?: number;
	worked?: number;
185 186 187
	message?: string;
}

188
export interface ISearchProgressItem extends IFileMatch, IProgress {
E
Erich Gamma 已提交
189 190 191
	// Marker interface to indicate the possible values for progress calls from the engine
}

192
export interface ISearchCompleteStats {
E
Erich Gamma 已提交
193
	limitHit?: boolean;
194
	stats?: IFileSearchStats | ITextSearchStats;
195 196 197
}

export interface ISearchComplete extends ISearchCompleteStats {
E
Erich Gamma 已提交
198
	results: IFileMatch[];
C
chrmarti 已提交
199 200
}

201 202 203 204
export interface ITextSearchStats {
	type: 'textSearchProvider' | 'searchProcess';
}

205
export interface IFileSearchStats {
206
	fromCache: boolean;
207
	detailStats: ISearchEngineStats | ICachedSearchStats | IFileSearchProviderStats | IFileIndexProviderStats;
208

209
	resultCount: number;
R
Rob Lourens 已提交
210
	type: 'fileIndexProvider' | 'fileSearchProvider' | 'searchProcess';
211
	sortingTime?: number;
212 213
}

214 215 216 217
export interface ICachedSearchStats {
	cacheWasResolved: boolean;
	cacheLookupTime: number;
	cacheFilterTime: number;
218 219 220
	cacheEntryCount: number;
}

221
export interface ISearchEngineStats {
C
Christof Marti 已提交
222
	traversal: string;
223
	fileWalkTime: number;
C
chrmarti 已提交
224 225
	directoriesWalked: number;
	filesWalked: number;
226
	cmdTime: number;
C
Christof Marti 已提交
227
	cmdResultCount?: number;
E
Erich Gamma 已提交
228 229
}

230 231 232 233 234 235 236 237 238 239 240 241 242
export interface IFileSearchProviderStats {
	providerTime: number;
	postProcessTime: number;
}

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

E
Erich Gamma 已提交
243
export class FileMatch implements IFileMatch {
244
	public matches: ITextSearchResult[] = [];
E
Erich Gamma 已提交
245 246 247 248 249
	constructor(public resource: uri) {
		// empty
	}
}

250 251 252 253
export class TextSearchResult implements ITextSearchResult {
	range: ISearchRange;
	preview: ITextSearchResultPreview;

254
	constructor(text: string, range: ISearchRange, previewOptions?: ITextSearchPreviewOptions) {
255
		this.range = range;
256 257 258

		if (previewOptions && previewOptions.matchLines === 1) {
			// 1 line preview requested
259
			text = getNLines(text, previewOptions.matchLines);
R
Rob Lourens 已提交
260
			const leadingChars = Math.floor(previewOptions.charsPerLine / 5);
261
			const previewStart = Math.max(range.startColumn - leadingChars, 0);
262 263 264 265 266
			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
267 268

			this.preview = {
269 270
				text: previewText,
				match: new OneLineRange(0, range.startColumn - previewStart, endColInPreview)
271 272
			};
		} else {
273
			// n line or no preview requested
274
			this.preview = {
275 276
				text,
				match: new SearchRange(0, range.startColumn, range.endLineNumber - range.startLineNumber, range.endColumn)
277 278 279 280 281
			};
		}
	}
}

282
export class SearchRange implements ISearchRange {
283 284 285 286 287
	startLineNumber: number;
	startColumn: number;
	endLineNumber: number;
	endColumn: number;

288 289
	constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number) {
		this.startLineNumber = startLineNumber;
290
		this.startColumn = startColumn;
291
		this.endLineNumber = endLineNumber;
292
		this.endColumn = endColumn;
E
Erich Gamma 已提交
293 294 295
	}
}

296 297 298 299 300 301
export class OneLineRange extends SearchRange {
	constructor(lineNumber: number, startColumn: number, endColumn: number) {
		super(lineNumber, startColumn, lineNumber, endColumn);
	}
}

302 303 304
export interface ISearchConfigurationProperties {
	exclude: glob.IExpression;
	useRipgrep: boolean;
305
	disableRipgrep: boolean;
306 307 308 309
	/**
	 * Use ignore file for file search.
	 */
	useIgnoreFiles: boolean;
P
pkoushik 已提交
310
	useGlobalIgnoreFiles: boolean;
311 312
	followSymlinks: boolean;
	smartCase: boolean;
313
	globalFindClipboard: boolean;
314
	location: 'sidebar' | 'panel';
315
	useReplacePreview: boolean;
316
	showLineNumbers: boolean;
R
Rob Lourens 已提交
317
	usePCRE2: boolean;
318 319
}

E
Erich Gamma 已提交
320
export interface ISearchConfiguration extends IFilesConfiguration {
321
	search: ISearchConfigurationProperties;
322 323 324
	editor: {
		wordSeparators: string;
	};
B
Benjamin Pasero 已提交
325 326
}

327
export function getExcludes(configuration: ISearchConfiguration): glob.IExpression | undefined {
B
Benjamin Pasero 已提交
328 329 330 331
	const fileExcludes = configuration && configuration.files && configuration.files.exclude;
	const searchExcludes = configuration && configuration.search && configuration.search.exclude;

	if (!fileExcludes && !searchExcludes) {
R
Rob Lourens 已提交
332
		return undefined;
B
Benjamin Pasero 已提交
333 334 335 336 337 338
	}

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

339
	let allExcludes: glob.IExpression = Object.create(null);
S
Sandeep Somavarapu 已提交
340
	// clone the config as it could be frozen
J
Johannes Rieken 已提交
341 342
	allExcludes = objects.mixin(allExcludes, objects.deepClone(fileExcludes));
	allExcludes = objects.mixin(allExcludes, objects.deepClone(searchExcludes), true);
B
Benjamin Pasero 已提交
343 344

	return allExcludes;
345
}
346

347 348
export function pathIncludedInQuery(queryProps: ICommonQueryProps<uri>, fsPath: string): boolean {
	if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) {
349 350 351
		return false;
	}

352
	if (queryProps.includePattern && !glob.match(queryProps.includePattern, fsPath)) {
353 354 355 356
		return false;
	}

	// If searchPaths are being used, the extra file must be in a subfolder and match the pattern, if present
357 358
	if (queryProps.usingSearchPaths) {
		return !!queryProps.folderQueries && queryProps.folderQueries.every(fq => {
359 360 361 362 363 364 365 366 367 368 369
			const searchPath = fq.folder.fsPath;
			if (paths.isEqualOrParent(fsPath, searchPath)) {
				return !fq.includePattern || !!glob.match(fq.includePattern, fsPath);
			} else {
				return false;
			}
		});
	}

	return true;
}