diff --git a/src/vs/workbench/parts/search/browser/patternInputWidget.ts b/src/vs/workbench/parts/search/browser/patternInputWidget.ts index 1748758781224db09ae3a4d498546a14cd4af6cc..7dc93c1d8d00d3c17a041c87b47b5bfe264a7a5a 100644 --- a/src/vs/workbench/parts/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/parts/search/browser/patternInputWidget.ts @@ -115,7 +115,6 @@ 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); @@ -123,7 +122,6 @@ export class PatternInputWidget extends Widget { exprSegments = groups.exprSegments; } else { const segments = pattern.split(',') - .map(s => strings.ltrim(s.trim(), './')) .filter(s => !!s.length); const groups = this.groupByPathsAndExprSegments(segments); @@ -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; } diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 5fff6e05f3cae2e269c50a402e29cbd3f8695371..b24862f5f20e0be769d028dd872e85b7ef0907a1 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -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'; @@ -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; + } + } } } @@ -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 diff --git a/src/vs/workbench/parts/search/common/searchQuery.ts b/src/vs/workbench/parts/search/common/searchQuery.ts index 0c0462f65ef684f6900e4ce9a00c2da8d22a586c..37bb3150c226a0484b9197c3effb7e7b79c7d09a 100644 --- a/src/vs/workbench/parts/search/common/searchQuery.ts +++ b/src/vs/workbench/parts/search/common/searchQuery.ts @@ -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 { @@ -38,6 +43,8 @@ export class QueryBuilder { return folderConfig.search.useRipgrep; }); + const searchPaths = this.getSearchPaths(options).searchPaths; + return { type, folderQueries, @@ -52,7 +59,7 @@ export class QueryBuilder { useRipgrep, disregardIgnoreFiles: options.disregardIgnoreFiles, disregardExcludeSettings: options.disregardExcludeSettings, - searchPaths: options.searchPaths + searchPaths }; } @@ -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 }; + } } \ No newline at end of file