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

7
// import nls = require('vs/nls');
8
import * as arrays from 'vs/base/common/arrays';
R
Rob Lourens 已提交
9 10
import * as collections from 'vs/base/common/collections';
import * as glob from 'vs/base/common/glob';
11
import * as paths from 'vs/base/common/paths';
R
Rob Lourens 已提交
12
import * as strings from 'vs/base/common/strings';
R
Rob Lourens 已提交
13
import uri from 'vs/base/common/uri';
14
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
15
import { IPatternInfo, IQueryOptions, IFolderQuery, ISearchQuery, QueryType, ISearchConfiguration, getExcludes } from 'vs/platform/search/common/search';
J
Johannes Rieken 已提交
16
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
E
Erich Gamma 已提交
17

18 19 20 21 22
interface ISearchPathPattern {
	searchPath: uri;
	pattern?: string;
}

E
Erich Gamma 已提交
23 24
export class QueryBuilder {

25 26 27
	constructor(
		@IConfigurationService private configurationService: IConfigurationService,
		@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService) {
E
Erich Gamma 已提交
28 29
	}

R
Rob Lourens 已提交
30 31
	public text(contentPattern: IPatternInfo, folderResources?: uri[], options?: IQueryOptions): ISearchQuery {
		return this.query(QueryType.Text, contentPattern, folderResources, options);
E
Erich Gamma 已提交
32 33
	}

R
Rob Lourens 已提交
34 35
	public file(folderResources?: uri[], options?: IQueryOptions): ISearchQuery {
		return this.query(QueryType.File, null, folderResources, options);
E
Erich Gamma 已提交
36 37
	}

R
Rob Lourens 已提交
38
	private query(type: QueryType, contentPattern: IPatternInfo, folderResources?: uri[], options: IQueryOptions = {}): ISearchQuery {
39
		let { searchPaths, includePattern } = this.getSearchPaths(options.includePattern);
40 41

		// Build folderQueries from searchPaths, if given, otherwise folderResources
42 43
		searchPaths = (searchPaths && searchPaths.length) ?
			searchPaths :
44
			folderResources && folderResources.map(fr => <ISearchPathPattern>{ searchPath: fr });
45

46
		const folderQueries = searchPaths && searchPaths.map(searchPath => {
47 48 49 50 51 52
			const folderQuery = this.getFolderQuery(searchPath.searchPath, options);
			if (searchPath.pattern) {
				folderQuery.includePattern = patternListToIExpression([searchPath.pattern]);
			}

			return folderQuery;
R
Rob Lourens 已提交
53
		});
E
Erich Gamma 已提交
54

R
Rob Lourens 已提交
55 56 57 58
		const useRipgrep = !folderResources || folderResources.every(folder => {
			const folderConfig = this.configurationService.getConfiguration<ISearchConfiguration>(undefined, { resource: folder });
			return folderConfig.search.useRipgrep;
		});
59

R
Rob Lourens 已提交
60
		const excludePattern = patternListToIExpression(splitGlobPattern(options.excludePattern));
61

62
		return {
R
Rob Lourens 已提交
63 64
			type,
			folderQueries,
65 66
			extraFileResources: options.extraFileResources,
			filePattern: options.filePattern,
R
Rob Lourens 已提交
67
			excludePattern,
68
			includePattern,
69
			maxResults: options.maxResults,
70 71
			sortByScore: options.sortByScore,
			cacheKey: options.cacheKey,
72
			contentPattern: contentPattern,
R
Rob Lourens 已提交
73
			useRipgrep,
74
			disregardIgnoreFiles: options.disregardIgnoreFiles,
75
			disregardExcludeSettings: options.disregardExcludeSettings
76
		};
E
Erich Gamma 已提交
77
	}
R
Rob Lourens 已提交
78

R
Rob Lourens 已提交
79 80 81 82
	/**
	 * Take the includePattern as seen in the search viewlet, and split into components that look like searchPaths, and
	 * glob patterns. Glob patterns are expanded from 'foo/bar' to '{foo/bar/**, **\/foo/bar}
	 */
83
	private getSearchPaths(pattern: string): { searchPaths: ISearchPathPattern[]; includePattern: glob.IExpression } {
R
Rob Lourens 已提交
84 85 86 87
		const isSearchPath = (segment: string) => {
			// A segment is a search path if it is an absolute path or starts with ./
			return paths.isAbsolute(segment) || strings.startsWith(segment, './');
		};
R
Rob Lourens 已提交
88

R
Rob Lourens 已提交
89 90 91 92
		const segments = splitGlobPattern(pattern);
		const groups = collections.groupBy(segments,
			segment => isSearchPath(segment) ? 'searchPaths' : 'exprSegments');

93
		const exprSegments = (groups.exprSegments || [])
R
Rob Lourens 已提交
94 95 96 97 98 99 100 101 102
			.map(p => {
				if (p[0] === '.') {
					p = '*' + p; // convert ".js" to "*.js"
				}

				return strings.format('{{0}/**,**/{1}}', p, p); // convert foo to {foo/**,**/foo} to cover files and folders
			});

		return {
103 104
			searchPaths: this.expandSearchPathPatterns(groups.searchPaths),
			includePattern: patternListToIExpression(exprSegments)
R
Rob Lourens 已提交
105 106 107 108 109 110 111
		};
	}

	private getExcludesForFolder(folderConfig: ISearchConfiguration, options: IQueryOptions): glob.IExpression | null {
		return options.disregardExcludeSettings ?
			null :
			getExcludes(folderConfig);
R
Rob Lourens 已提交
112
	}
113

R
Rob Lourens 已提交
114 115 116
	/**
	 * Split search paths (./ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths
	 */
117 118
	private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] {
		if (!this.workspaceContextService.hasWorkspace() || !searchPaths || !searchPaths.length) {
119
			// No workspace => ignore search paths
120
			return [];
121 122
		}

123
		return arrays.flatten(searchPaths.map(searchPath => {
124 125
			// 1 open folder => just resolve the search paths to absolute paths
			const { pathPortion, globPortion } = splitGlobFromPath(searchPath);
126 127 128 129 130 131 132 133
			const pathPortions = this.expandAbsoluteSearchPaths(pathPortion);
			return pathPortions.map(searchPath => {
				return <ISearchPathPattern>{
					searchPath: uri.parse(searchPath),
					pattern: globPortion && paths.join(searchPath, globPortion)
				};
			});
		}));
134 135
	}

136 137 138
	/**
	 * Takes a searchPath like `./a/foo` and expands it to absolute paths for all the workspaces it matches.
	 */
139 140 141 142
	private expandAbsoluteSearchPaths(searchPath: string): string[] {
		if (paths.isAbsolute(searchPath)) {
			return [searchPath];
		}
143

144 145 146 147 148 149 150 151 152 153
		const workspace = this.workspaceContextService.getWorkspace();
		if (workspace.roots.length === 1) {
			return [paths.join(workspace.roots[0].fsPath, searchPath)];
		} else {
			const relativeSearchPathMatch = searchPath.match(/\.\/([^\/]+)(\/.+)?/);
			if (relativeSearchPathMatch) {
				const searchPathRoot = relativeSearchPathMatch[1];
				const matchingRoots = workspace.roots.filter(root => paths.basename(root.fsPath) === searchPathRoot);
				if (matchingRoots.length) {
					return matchingRoots.map(root => paths.join(root.fsPath, relativeSearchPathMatch[2] || ''));
154
				} else {
155
					// throw new Error(nls.localize('search.invalidRootFolder', 'No root folder named {}', searchPathRoot));
156
				}
157 158 159
			} else {
				// Malformed ./ search path
				// throw new Error(nls.localize('search.invalidRelativeInclude', 'Invalid folder include pattern: {}', searchPath));
160 161 162
			}
		}

163
		return [];
164
	}
165 166 167 168 169 170 171 172 173

	private getFolderQuery(folder: uri, options?: IQueryOptions): IFolderQuery {
		const folderConfig = this.configurationService.getConfiguration<ISearchConfiguration>(undefined, { resource: folder });
		return <IFolderQuery>{
			folder,
			excludePattern: this.getExcludesForFolder(folderConfig, options),
			fileEncoding: folderConfig.files.encoding
		};
	}
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
}

function splitGlobFromPath(searchPath: string): { pathPortion: string, globPortion?: string } {
	const globCharMatch = searchPath.match(/[\*\{\}\(\)\[\]\?]/);
	if (globCharMatch) {
		const globCharIdx = globCharMatch.index;
		const lastSlashMatch = searchPath.substr(0, globCharIdx).match(/[/|\\][^/\\]*$/);
		if (lastSlashMatch) {
			return {
				pathPortion: searchPath.substr(0, lastSlashMatch.index),
				globPortion: searchPath.substr(lastSlashMatch.index + 1)
			};
		}
	}

	// No glob char, or malformed
	return {
		pathPortion: searchPath
	};
193
}
R
Rob Lourens 已提交
194 195 196 197 198 199 200 201 202 203

function patternListToIExpression(patterns: string[]): glob.IExpression {
	return patterns.reduce((glob, cur) => { glob[cur] = true; return glob; }, Object.create(null));
}

function splitGlobPattern(pattern: string): string[] {
	return glob.splitGlobAware(pattern, ',')
		.map(s => s.trim())
		.filter(s => !!s.length);
}