Skip to content

Commit

Permalink
Implement ./ and absolute path searching per #27226
Browse files Browse the repository at this point in the history
  • Loading branch information
roblourens committed Jul 14, 2017
1 parent 457fa64 commit 19bc6c2
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 14 deletions.
9 changes: 3 additions & 6 deletions src/vs/workbench/parts/search/browser/patternInputWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,13 @@ export class PatternInputWidget extends Widget {
let searchPaths: string[];
if (isGlobPattern) {
const segments = splitGlobAware(pattern, ',')
.map(s => strings.ltrim(s.trim(), './'))
.filter(s => !!s.length);

const groups = this.groupByPathsAndExprSegments(segments);
searchPaths = groups.searchPaths;
exprSegments = groups.exprSegments;
} else {
const segments = pattern.split(',')
.map(s => strings.ltrim(s.trim(), './'))
.filter(s => !!s.length);

const groups = this.groupByPathsAndExprSegments(segments);
Expand All @@ -144,15 +142,14 @@ export class PatternInputWidget extends Widget {

private groupByPathsAndExprSegments(segments: string[]) {
const isSearchPath = (segment: string) => {
// An segment is a search path if it is an absolute path and doesn't contain any glob characters
return paths.isAbsolute(segment) && !segment.match(/[\*\{\}\(\)\[\]\?]/);
// A segment is a search path if it is an absolute path or starts with ./
return paths.isAbsolute(segment) || strings.startsWith(segment, './');
};

const groups = collections.groupBy(segments,
segment => isSearchPath(segment) ? 'searchPaths' : 'exprSegments');
groups.searchPaths = groups.searchPaths || [];
groups.exprSegments = (groups.exprSegments || [])
.map(segment => strings.trim(segment, '/'));
groups.exprSegments = groups.exprSegments || [];

return groups;
}
Expand Down
27 changes: 23 additions & 4 deletions src/vs/workbench/parts/search/browser/searchViewlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import env = require('vs/base/common/platform');
import { Delayer } from 'vs/base/common/async';
import URI from 'vs/base/common/uri';
import strings = require('vs/base/common/strings');
import * as paths from 'vs/base/common/paths';
import dom = require('vs/base/browser/dom');
import { IAction, Action } from 'vs/base/common/actions';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
Expand Down Expand Up @@ -897,13 +898,24 @@ export class SearchViewlet extends Viewlet {
const workspace = this.contextService.getWorkspace();
if (workspace) {
if (workspace.roots.length === 1) {
// Fallback to old way for single root workspace
folderPath = this.contextService.toWorkspaceRelativePath(resource);
// Show relative path from the root for single-root mode
folderPath = paths.relative(workspace.roots[0].fsPath, resource.fsPath);
if (folderPath && folderPath !== '.') {
folderPath = './' + folderPath;
}
} else {
folderPath = resource.fsPath;
const owningRoot = this.contextService.getRoot(resource);
if (owningRoot) {
const owningRootBasename = paths.basename(owningRoot.fsPath);

// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
const isUniqueRoot = workspace.roots.filter(root => paths.basename(root.fsPath) === owningRootBasename).length === 1;
if (isUniqueRoot) {
folderPath = `./${owningRootBasename}/${paths.relative(owningRoot.fsPath, resource.fsPath)}`;
} else {
folderPath = resource.fsPath;
}
}
}
}

Expand Down Expand Up @@ -977,7 +989,14 @@ export class SearchViewlet extends Viewlet {
};

const folderResources = this.contextService.hasWorkspace() ? this.contextService.getWorkspace().roots : [];
this.onQueryTriggered(this.queryBuilder.text(content, folderResources, options), excludePatternText, includePatternText);
let query: ISearchQuery;
// try {
query = this.queryBuilder.text(content, folderResources, options);
// } catch (e) {
// // TODO@roblou show error popup
// }

this.onQueryTriggered(query, excludePatternText, includePatternText);

if (!preserveFocus) {
this.searchWidget.focus(false); // focus back to input field
Expand Down
70 changes: 66 additions & 4 deletions src/vs/workbench/parts/search/common/searchQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
*--------------------------------------------------------------------------------------------*/
'use strict';

import nls = require('vs/nls');
import { IExpression } from 'vs/base/common/glob';
import { mixin } from 'vs/base/common/objects';
import * as objects from 'vs/base/common/objects';
import * as paths from 'vs/base/common/paths';
import uri from 'vs/base/common/uri';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IPatternInfo, IQueryOptions, IFolderQueryOptions, ISearchQuery, QueryType, ISearchConfiguration, getExcludes } from 'vs/platform/search/common/search';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';

export class QueryBuilder {

constructor( @IConfigurationService private configurationService: IConfigurationService) {
constructor(
@IConfigurationService private configurationService: IConfigurationService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService) {
}

public text(contentPattern: IPatternInfo, folderResources?: uri[], options?: IQueryOptions): ISearchQuery {
Expand All @@ -38,6 +43,8 @@ export class QueryBuilder {
return folderConfig.search.useRipgrep;
});

const searchPaths = this.getSearchPaths(options).searchPaths;

return {
type,
folderQueries,
Expand All @@ -52,7 +59,7 @@ export class QueryBuilder {
useRipgrep,
disregardIgnoreFiles: options.disregardIgnoreFiles,
disregardExcludeSettings: options.disregardExcludeSettings,
searchPaths: options.searchPaths
searchPaths
};
}

Expand All @@ -62,9 +69,64 @@ export class QueryBuilder {
if (options.disregardExcludeSettings) {
return null;
} else if (options.excludePattern) {
return mixin(options.excludePattern, settingsExcludePattern, false /* no overwrite */);
return objects.mixin(options.excludePattern, settingsExcludePattern, false /* no overwrite */);
} else {
return settingsExcludePattern;
}
}

private getSearchPaths(options: IQueryOptions): { searchPaths: string[], additionalIncludePatterns: string[] } {
if (!this.workspaceContextService.hasWorkspace() || !options.searchPaths) {
// No workspace => ignore search paths
return {
searchPaths: [],
additionalIncludePatterns: []
};
}

const workspace = this.workspaceContextService.getWorkspace();
if (workspace.roots.length < 2) {
// 1 open folder => just resolve the search paths to absolute paths
const searchPaths = options.searchPaths.map(searchPath => {
const relativeSearchPathMatch = searchPath.match(/\.\/(.+)/);
if (relativeSearchPathMatch) {
return paths.join(workspace.roots[0].fsPath, relativeSearchPathMatch[1]);
} else {
return null;
}
});

return {
searchPaths,
additionalIncludePatterns: []
};
}

// Is a multiroot workspace
const searchPaths: string[] = [];
const additionalIncludePatterns: string[] = [];

// Resolve searchPaths, relative or absolute, against roots
for (const searchPath of options.searchPaths) {
if (paths.isAbsolute(searchPath)) {
searchPaths.push(searchPath); // later, pull out globs
} 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) {
throw new Error(nls.localize('search.invalidRootFolder', 'No root folder named {}', searchPathRoot));
}

searchPaths.push(...matchingRoots.map(root => paths.join(root.fsPath, relativeSearchPathMatch[2])));
} else {
// Malformed ./ search path
throw new Error(nls.localize('search.invalidRelativeInclude', 'Invalid folder include pattern: {}', searchPath));
}
}
}

return { searchPaths, additionalIncludePatterns };
}
}

0 comments on commit 19bc6c2

Please sign in to comment.