search.ts 10.5 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
	search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete>;
29
	extendQuery(query: ISearchQuery): void;
30
	clearCache(cacheKey: string): TPromise<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 {
59
	search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete>;
60
	clearCache(cacheKey: string): TPromise<void>;
E
Erich Gamma 已提交
61 62
}

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

72 73
export interface ICommonQueryOptions<U> {
	extraFileResources?: U[];
R
Rob Lourens 已提交
74
	filePattern?: string; // file search only
R
Rob Lourens 已提交
75
	fileEncoding?: string;
E
Erich Gamma 已提交
76
	maxResults?: number;
77 78 79 80 81 82
	/**
	 * 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;
83 84
	sortByScore?: boolean;
	cacheKey?: string;
85
	useRipgrep?: boolean;
R
Rob Lourens 已提交
86
	disregardIgnoreFiles?: boolean;
P
pkoushik 已提交
87
	disregardGlobalIgnoreFiles?: boolean;
R
Rob Lourens 已提交
88
	disregardExcludeSettings?: boolean;
89
	ignoreSymlinks?: boolean;
90
	maxFileSize?: number;
91
	previewOptions?: ITextSearchPreviewOptions;
E
Erich Gamma 已提交
92 93
}

94
export interface IQueryOptions extends ICommonQueryOptions<uri> {
R
Rob Lourens 已提交
95 96 97 98
	excludePattern?: string;
	includePattern?: string;
}

99
export interface ISearchQueryProps<U extends UriComponents> extends ICommonQueryOptions<U> {
E
Erich Gamma 已提交
100
	type: QueryType;
R
Rob Lourens 已提交
101

102 103
	excludePattern?: glob.IExpression;
	includePattern?: glob.IExpression;
E
Erich Gamma 已提交
104
	contentPattern?: IPatternInfo;
105
	folderQueries?: IFolderQuery<U>[];
106
	usingSearchPaths?: boolean;
E
Erich Gamma 已提交
107 108
}

109 110 111
export type ISearchQuery = ISearchQueryProps<uri>;
export type IRawSearchQuery = ISearchQueryProps<UriComponents>;

112
export const enum QueryType {
E
Erich Gamma 已提交
113 114 115
	File = 1,
	Text = 2
}
K
kieferrm 已提交
116
/* __GDPR__FRAGMENT__
K
kieferrm 已提交
117 118
	"IPatternInfo" : {
		"pattern" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
K
kieferrm 已提交
119 120
		"isRegExp": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
		"isWordMatch": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
K
kieferrm 已提交
121
		"wordSeparators": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
122 123 124
		"isMultiline": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
		"isCaseSensitive": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
		"isSmartCase": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
K
kieferrm 已提交
125 126
	}
*/
E
Erich Gamma 已提交
127 128 129 130
export interface IPatternInfo {
	pattern: string;
	isRegExp?: boolean;
	isWordMatch?: boolean;
131
	wordSeparators?: string;
S
Sandeep Somavarapu 已提交
132
	isMultiline?: boolean;
E
Erich Gamma 已提交
133
	isCaseSensitive?: boolean;
134
	isSmartCase?: boolean;
E
Erich Gamma 已提交
135 136
}

137
export interface IFileMatch<U extends UriComponents = uri> {
138
	resource?: U;
139
	matches?: ITextSearchResult[];
E
Erich Gamma 已提交
140 141
}

142
export type IRawFileMatch2 = IFileMatch<UriComponents>;
143

144
export interface ITextSearchPreviewOptions {
R
Rob Lourens 已提交
145 146
	matchLines: number;
	charsPerLine: number;
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
}

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 已提交
165 166 167 168 169
}

export interface IProgress {
	total?: number;
	worked?: number;
170 171 172
	message?: string;
}

173
export interface ISearchProgressItem extends IFileMatch, IProgress {
E
Erich Gamma 已提交
174 175 176
	// Marker interface to indicate the possible values for progress calls from the engine
}

177
export interface ISearchCompleteStats {
E
Erich Gamma 已提交
178
	limitHit?: boolean;
179
	stats?: IFileSearchStats | ITextSearchStats;
180 181 182
}

export interface ISearchComplete extends ISearchCompleteStats {
E
Erich Gamma 已提交
183
	results: IFileMatch[];
C
chrmarti 已提交
184 185
}

186 187 188 189
export interface ITextSearchStats {
	type: 'textSearchProvider' | 'searchProcess';
}

190
export interface IFileSearchStats {
191
	fromCache: boolean;
192
	detailStats: ISearchEngineStats | ICachedSearchStats | IFileSearchProviderStats | IFileIndexProviderStats;
193

194
	resultCount: number;
R
Rob Lourens 已提交
195
	type: 'fileIndexProvider' | 'fileSearchProvider' | 'searchProcess';
196
	sortingTime?: number;
197 198
}

199 200 201 202
export interface ICachedSearchStats {
	cacheWasResolved: boolean;
	cacheLookupTime: number;
	cacheFilterTime: number;
203 204 205
	cacheEntryCount: number;
}

206
export interface ISearchEngineStats {
C
Christof Marti 已提交
207
	traversal: string;
208
	fileWalkTime: number;
C
chrmarti 已提交
209 210
	directoriesWalked: number;
	filesWalked: number;
211
	cmdTime: number;
C
Christof Marti 已提交
212
	cmdResultCount?: number;
E
Erich Gamma 已提交
213 214
}

215 216 217 218 219 220 221 222 223 224 225 226 227
export interface IFileSearchProviderStats {
	providerTime: number;
	postProcessTime: number;
}

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

E
Erich Gamma 已提交
228
export class FileMatch implements IFileMatch {
229
	public matches: ITextSearchResult[] = [];
E
Erich Gamma 已提交
230 231 232 233 234
	constructor(public resource: uri) {
		// empty
	}
}

235 236 237 238
export class TextSearchResult implements ITextSearchResult {
	range: ISearchRange;
	preview: ITextSearchResultPreview;

239
	constructor(text: string, range: ISearchRange, previewOptions?: ITextSearchPreviewOptions) {
240 241
		this.range = range;
		if (previewOptions) {
242
			text = getNLines(text, previewOptions.matchLines);
R
Rob Lourens 已提交
243
			const leadingChars = Math.floor(previewOptions.charsPerLine / 5);
244 245 246 247 248
			const endColumnByTrimmedLines = (range.startLineNumber + previewOptions.matchLines - 1) === range.endLineNumber ? // if single line...
				range.endColumn :
				previewOptions.charsPerLine;

			// This doesn't handle all previewOptions correctly
249
			const previewStart = Math.max(range.startColumn - leadingChars, 0);
250 251
			const endByCharsPerLine = previewOptions.charsPerLine + previewStart;
			const trimmedEndOfMatchRangeInPreview = Math.min(endByCharsPerLine, endColumnByTrimmedLines - previewStart);
252 253

			this.preview = {
254 255
				text: text.substring(previewStart, endByCharsPerLine),
				match: new OneLineRange(0, range.startColumn - previewStart, trimmedEndOfMatchRangeInPreview)
256 257 258
			};
		} else {
			this.preview = {
259
				text: text,
260 261 262 263 264 265
				match: new OneLineRange(0, range.startColumn, range.endColumn)
			};
		}
	}
}

266
export class SearchRange implements ISearchRange {
267 268 269 270 271
	startLineNumber: number;
	startColumn: number;
	endLineNumber: number;
	endColumn: number;

272 273
	constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number) {
		this.startLineNumber = startLineNumber;
274
		this.startColumn = startColumn;
275
		this.endLineNumber = endLineNumber;
276
		this.endColumn = endColumn;
E
Erich Gamma 已提交
277 278 279
	}
}

280 281 282 283 284 285
export class OneLineRange extends SearchRange {
	constructor(lineNumber: number, startColumn: number, endColumn: number) {
		super(lineNumber, startColumn, lineNumber, endColumn);
	}
}

286 287 288 289 290 291 292
export interface ISearchConfigurationProperties {
	exclude: glob.IExpression;
	useRipgrep: boolean;
	/**
	 * Use ignore file for file search.
	 */
	useIgnoreFiles: boolean;
P
pkoushik 已提交
293
	useGlobalIgnoreFiles: boolean;
294 295
	followSymlinks: boolean;
	smartCase: boolean;
296
	globalFindClipboard: boolean;
297
	location: 'sidebar' | 'panel';
298
	useReplacePreview: boolean;
299
	showLineNumbers: boolean;
300 301
}

E
Erich Gamma 已提交
302
export interface ISearchConfiguration extends IFilesConfiguration {
303
	search: ISearchConfigurationProperties;
304 305 306
	editor: {
		wordSeparators: string;
	};
B
Benjamin Pasero 已提交
307 308
}

309
export function getExcludes(configuration: ISearchConfiguration): glob.IExpression | undefined {
B
Benjamin Pasero 已提交
310 311 312 313
	const fileExcludes = configuration && configuration.files && configuration.files.exclude;
	const searchExcludes = configuration && configuration.search && configuration.search.exclude;

	if (!fileExcludes && !searchExcludes) {
R
Rob Lourens 已提交
314
		return undefined;
B
Benjamin Pasero 已提交
315 316 317 318 319 320
	}

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

321
	let allExcludes: glob.IExpression = Object.create(null);
S
Sandeep Somavarapu 已提交
322
	// clone the config as it could be frozen
J
Johannes Rieken 已提交
323 324
	allExcludes = objects.mixin(allExcludes, objects.deepClone(fileExcludes));
	allExcludes = objects.mixin(allExcludes, objects.deepClone(searchExcludes), true);
B
Benjamin Pasero 已提交
325 326

	return allExcludes;
327
}
328 329 330 331 332 333 334 335 336 337 338 339

export function pathIncludedInQuery(query: ISearchQuery, fsPath: string): boolean {
	if (query.excludePattern && glob.match(query.excludePattern, fsPath)) {
		return false;
	}

	if (query.includePattern && !glob.match(query.includePattern, fsPath)) {
		return false;
	}

	// If searchPaths are being used, the extra file must be in a subfolder and match the pattern, if present
	if (query.usingSearchPaths) {
340
		return !!query.folderQueries && query.folderQueries.every(fq => {
341 342 343 344 345 346 347 348 349 350 351
			const searchPath = fq.folder.fsPath;
			if (paths.isEqualOrParent(fsPath, searchPath)) {
				return !fq.includePattern || !!glob.match(fq.includePattern, fsPath);
			} else {
				return false;
			}
		});
	}

	return true;
}