提交 ce886b83 编写于 作者: B Benjamin Pasero

Quick open filters inconsistently between editor history and file search (fixes #258)

上级 51b0071c
......@@ -23,7 +23,7 @@ export interface ISearchService {
export interface IQueryOptions {
rootResources?: uri[];
filePatterns?: IPatternInfo[];
filePattern?: string;
excludePattern?: glob.IExpression;
includePattern?: glob.IExpression;
maxResults?: number;
......
......@@ -50,7 +50,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
this.openSymbolHandler.setStandalone(false);
this.openFileHandler.setStandalone(false);
this.resultsToSearchCache = {};
this.resultsToSearchCache = Object.create(null);
this.delayer = new ThrottledDelayer(OpenAnythingHandler.SEARCH_DELAY);
}
......@@ -201,7 +201,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
// Find cache entries by prefix of search value
let cachedEntries: QuickOpenEntry[];
for (let previousSearch in this.resultsToSearchCache) {
if (this.resultsToSearchCache.hasOwnProperty(previousSearch) && searchValue.indexOf(previousSearch) === 0) {
if (searchValue.indexOf(previousSearch) === 0) {
cachedEntries = this.resultsToSearchCache[previousSearch];
break;
}
......@@ -257,7 +257,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
this.isClosed = true;
// Clear Cache
this.resultsToSearchCache = {};
this.resultsToSearchCache = Object.create(null);
// Propagate
this.openSymbolHandler.onClose(canceled);
......
......@@ -138,7 +138,7 @@ export class OpenFileHandler extends QuickOpenHandler {
rootResources.push(this.contextService.getWorkspace().resource);
}
let query: IQueryOptions = { filePatterns: [{ pattern: searchValue }], rootResources: rootResources };
let query: IQueryOptions = { filePattern: searchValue, rootResources: rootResources };
return this.queryBuilder.file(query).then((query) => {
this.pendingSearch = this.searchService.search(query);
......
......@@ -58,7 +58,7 @@ export class QueryBuilder {
return {
type: type,
rootResources: options.rootResources,
filePatterns: options.filePatterns || [],
filePattern: options.filePattern,
excludePattern: options.excludePattern,
includePattern: options.includePattern,
maxResults: options.maxResults,
......
......@@ -9,6 +9,7 @@ import fs = require('fs');
import paths = require('path');
import types = require('vs/base/common/types');
import filters = require('vs/base/common/filters');
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings');
import glob = require('vs/base/common/glob');
......@@ -18,159 +19,12 @@ import extfs = require('vs/base/node/extfs');
import flow = require('vs/base/node/flow');
import {ISerializedFileMatch, IRawSearch, ISearchEngine} from 'vs/workbench/services/search/node/rawSearchService';
export class CamelCaseExp implements IExpression {
private pattern: string;
constructor(pattern: string) {
this.pattern = pattern.toLowerCase();
}
public test(value: string): boolean {
if (value.length === 0) {
return false;
}
let pattern = this.pattern.toLowerCase();
let result: boolean;
let i = 0;
while (i < value.length && !(result = matches(pattern, value, 0, i))) {
i = nextAnchor(value, i + 1);
}
return result;
}
}
function isUpper(c: string): boolean {
let code = c.charCodeAt(0);
return 65 <= code && code <= 90;
}
function isNumber(c: string): boolean {
let code = c.charCodeAt(0);
return 48 <= code && code <= 57;
}
function nextAnchor(value: string, start: number): number {
let c: string;
for (let i = start; i < value.length; i++) {
c = value[i];
if (isUpper(c) || isNumber(c)) {
return i;
}
}
return value.length;
}
function matches(pattern: string, value: string, patternIndex: number, valueIndex: number): boolean {
if (patternIndex === pattern.length) {
return true;
}
if (valueIndex === value.length) {
return false;
}
if (pattern[patternIndex] !== value[valueIndex].toLowerCase()) {
return false;
}
let nextUpperIndex = valueIndex + 1;
let result = matches(pattern, value, patternIndex + 1, valueIndex + 1);
while (!result && (nextUpperIndex = nextAnchor(value, nextUpperIndex)) < value.length) {
result = matches(pattern, value, patternIndex + 1, nextUpperIndex);
nextUpperIndex++;
}
return result;
}
function isCamelCasePattern(pattern: string): boolean {
return (/^\w[\w.]*$/).test(pattern);
}
export interface IExpression {
test: (value: string) => boolean;
}
export class FilePatterns {
private static DOT = '.'.charCodeAt(0);
private expressions: IExpression[];
constructor(expressions: IPatternInfo[]) {
this.expressions = [];
for (let i = 0; i < expressions.length; i++) {
let expression = expressions[i];
let exp: IExpression;
// Match all
if (!expression.pattern) {
exp = { test: () => true };
}
// RegExp
else if (expression.isRegExp) {
try {
exp = new RegExp(expression.pattern, 'i');
} catch (e) {
if (e instanceof SyntaxError) {
exp = { test: () => true };
} else {
throw e;
}
}
}
// Camelcase
else if (isCamelCasePattern(expression.pattern)) {
exp = new CamelCaseExp(expression.pattern);
}
// String
else {
if (expression.pattern.charCodeAt(0) === FilePatterns.DOT) {
expression.pattern = '*' + expression.pattern; // convert a .<something> to a *.<something> query
}
// escape to regular expressions
expression.pattern = strings.anchorPattern(strings.convertSimple2RegExpPattern(expression.pattern), true, false);
exp = new RegExp(expression.pattern, 'i');
}
this.expressions.push(exp);
}
}
public static hasPatterns(expressions: IPatternInfo[]): boolean {
return expressions && expressions.length > 0 && expressions.some(e => !!e.pattern);
}
public test(value: string): IExpression {
for (let i = 0; i < this.expressions.length; i++) {
let exp = this.expressions[i];
if (exp.test(value)) {
return exp;
}
}
return null;
}
}
export class FileWalker {
private static ENOTDIR = 'ENOTDIR';
private config: IRawSearch;
private patterns: FilePatterns;
private filePattern: string;
private excludePattern: glob.IExpression;
private includePattern: glob.IExpression;
private maxResults: number;
......@@ -182,7 +36,7 @@ export class FileWalker {
constructor(config: IRawSearch) {
this.config = config;
this.patterns = FilePatterns.hasPatterns(config.filePatterns) && new FilePatterns(config.filePatterns);
this.filePattern = config.filePattern;
this.excludePattern = config.excludePattern;
this.includePattern = config.includePattern;
this.maxResults = config.maxResults || null;
......@@ -227,7 +81,7 @@ export class FileWalker {
}
// Check for match on file pattern and include pattern
if ((!this.patterns || this.patterns.test(paths.basename(absolutePath))) && (!this.includePattern || glob.match(this.includePattern, absolutePath))) {
if (this.isFilePatternMatch(paths.basename(absolutePath)) && (!this.includePattern || glob.match(this.includePattern, absolutePath))) {
this.resultCount++;
if (this.maxResults && this.resultCount > this.maxResults) {
......@@ -264,7 +118,7 @@ export class FileWalker {
// to ignore filtering by siblings because the user seems to know what she
// is searching for and we want to include the result in that case anyway
let siblings = files;
if (this.config.filePatterns && this.config.filePatterns.length === 1 && this.config.filePatterns[0].pattern === file) {
if (this.config.filePattern === file) {
siblings = [];
}
......@@ -302,7 +156,7 @@ export class FileWalker {
if ((<any>error).code === FileWalker.ENOTDIR && !this.isCanceled && !this.isLimitHit) {
// Check for match on file pattern and include pattern
if ((!this.patterns || this.patterns.test(file)) && (!this.includePattern || glob.match(this.includePattern, relativeFilePath, children))) {
if (this.isFilePatternMatch(file) && (!this.includePattern || glob.match(this.includePattern, relativeFilePath, children))) {
this.resultCount++;
if (this.maxResults && this.resultCount > this.maxResults) {
......@@ -329,6 +183,19 @@ export class FileWalker {
});
}
private isFilePatternMatch(path: string): boolean {
// Check for search pattern
if (this.filePattern) {
const res = filters.matchesFuzzy(this.filePattern, path);
return !!res && res.length > 0;
}
// No patterns means we match all
return true;
}
private realPathLink(path: string, clb: (error: Error, realpath?: string) => void): void {
return fs.lstat(path, (error, lstat) => {
if (error) {
......
......@@ -18,7 +18,7 @@ import {Engine as TextSearchEngine} from 'vs/workbench/services/search/node/text
export interface IRawSearch {
rootPaths: string[];
filePatterns: IPatternInfo[];
filePattern?: string;
excludePattern?: glob.IExpression;
includePattern?: glob.IExpression;
contentPattern?: IPatternInfo;
......@@ -52,21 +52,17 @@ export interface ISerializedSearchProgressItem extends ISerializedFileMatch, IPr
export class SearchService implements IRawSearchService {
public fileSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
config.filePatterns = config.filePatterns && config.filePatterns.length > 0 ? config.filePatterns : [{ pattern: '' /* All files */ }];
let engine = new FileSearchEngine(config);
return this.doSearch(engine);
}
public textSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
config.filePatterns = config.filePatterns && config.filePatterns.length > 0 ? config.filePatterns : [{ pattern: '' /* All files */ }];
let engine = new TextSearchEngine(config, new FileWalker({
rootPaths: config.rootPaths,
includePattern: config.includePattern,
excludePattern: config.excludePattern,
filePatterns: config.filePatterns
filePattern: config.filePattern
}));
return this.doSearch(engine);
......
......@@ -8,8 +8,9 @@ import {PPromise} from 'vs/base/common/winjs.base';
import uri from 'vs/base/common/uri';
import glob = require('vs/base/common/glob');
import objects = require('vs/base/common/objects');
import filters = require('vs/base/common/filters');
import strings = require('vs/base/common/strings');
import { Client } from 'vs/base/node/service.cp';
import {Client} from 'vs/base/node/service.cp';
import {IProgress, LineMatch, FileMatch, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchQuery, ISearchConfiguration, ISearchService} from 'vs/platform/search/common/search';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/browser/untitledEditorService';
import {IModelService} from 'vs/editor/common/services/modelService';
......@@ -123,7 +124,7 @@ export class SearchService implements ISearchService {
return;
}
if (!this.matches(resource, query.filePatterns.map((p) => this.patternToRegExp(p.pattern)), query.includePattern, query.excludePattern)) {
if (!this.matches(resource, query.filePattern, query.includePattern, query.excludePattern)) {
return; // respect user filters
}
......@@ -145,27 +146,17 @@ export class SearchService implements ISearchService {
return localResults;
}
private patternToRegExp(pattern: string): RegExp {
if (pattern[0] === '.') {
pattern = '*' + pattern; // convert a .<something> to a *.<something> query
}
// escape to regular expressions
pattern = strings.anchorPattern(strings.convertSimple2RegExpPattern(pattern), true, false);
return new RegExp(pattern, 'i');
}
private matches(resource: uri, filePatterns: RegExp[], includePattern: glob.IExpression, excludePattern: glob.IExpression): boolean {
private matches(resource: uri, filePattern: string, includePattern: glob.IExpression, excludePattern: glob.IExpression): boolean {
let workspaceRelativePath = this.contextService.toWorkspaceRelativePath(resource);
// file patterns
if (filePatterns && filePatterns.length) {
// file pattern
if (filePattern) {
if (resource.scheme !== 'file') {
return false; // if we match on file pattern, we have to ignore non file resources
}
if (!filePatterns.some((pattern) => pattern.test(resource.fsPath))) {
const res = filters.matchesFuzzy(filePattern, resource.fsPath);
if (!res || res.length === 0) {
return false;
}
}
......@@ -229,7 +220,7 @@ class DiskSearch {
let rawSearch: IRawSearch = {
rootPaths: rootResources.map(r => r.fsPath),
filePatterns: query.filePatterns,
filePattern: query.filePattern,
excludePattern: query.excludePattern,
includePattern: query.includePattern,
maxResults: query.maxResults
......
......@@ -32,7 +32,7 @@ suite('Search', () => {
test('Files: *.js', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.js' }]
filePattern: '*.js'
});
let count = 0;
......@@ -50,7 +50,7 @@ suite('Search', () => {
test('Files: *.js (Files as roots)', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures/examples/company.js'), require.toUrl('./fixtures/examples/small.js')],
filePatterns: [{ pattern: '*.js' }]
filePattern: '*.js'
});
let count = 0;
......@@ -68,7 +68,7 @@ suite('Search', () => {
test('Files: NPE (CamelCase)', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: 'NullPE' }]
filePattern: 'NullPE'
});
let count = 0;
......@@ -86,7 +86,7 @@ suite('Search', () => {
test('Files: *.*', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', isCaseSensitive: true }]
filePattern: '*.*'
});
let count = 0;
......@@ -104,7 +104,7 @@ suite('Search', () => {
test('Files: *.as', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.as' }]
filePattern: '*.as'
});
let count = 0;
......@@ -123,7 +123,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.js', modifiers: 'i' }],
filePattern: '*.js',
contentPattern: { pattern: 'GameOfLife', modifiers: 'i' }
};
......@@ -144,7 +144,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.js', modifiers: 'i' }],
filePattern: '*.js',
contentPattern: { pattern: 'Game.?fL\\w?fe', isRegExp: true }
};
......@@ -165,7 +165,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.js', modifiers: 'i' }],
filePattern: '*.js',
contentPattern: { pattern: 'GameOfLife', isWordMatch: true, isCaseSensitive: true }
};
......@@ -186,7 +186,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.css', modifiers: 'i' }],
filePattern: '*.css',
contentPattern: { pattern: 'Helvetica', modifiers: 'i' }
};
......@@ -207,7 +207,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', modifiers: 'i' }],
filePattern: '*.*',
contentPattern: { pattern: 'e', modifiers: 'i' }
};
......@@ -228,7 +228,7 @@ suite('Search', () => {
let c = 0;
let config:any = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', modifiers: 'i' }],
filePattern: '*.*',
contentPattern: { pattern: 'e', modifiers: 'i' },
excludePattern: { '**/examples': true }
};
......@@ -250,7 +250,7 @@ suite('Search', () => {
let c = 0;
let config:any = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', modifiers: 'i' }],
filePattern: '*.*',
contentPattern: { pattern: 'e', modifiers: 'i' },
includePattern: { '**/examples/**': true }
};
......@@ -272,7 +272,7 @@ suite('Search', () => {
let c = 0;
let config:any = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', modifiers: 'i' }],
filePattern: '*.*',
contentPattern: { pattern: 'e', modifiers: 'i' },
includePattern: { '**/examples/**': true },
excludePattern: { '**/examples/small.js': true }
......@@ -295,7 +295,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', modifiers: 'i' }],
filePattern: '*.*',
contentPattern: { pattern: 'a', modifiers: 'i' },
maxResults: 520
};
......@@ -317,7 +317,7 @@ suite('Search', () => {
let c = 0;
let config = {
rootPaths: [require.toUrl('./fixtures')],
filePatterns: [{ pattern: '*.*', modifiers: 'i' }],
filePattern: '*.*',
contentPattern: { pattern: 'ahsogehtdas', modifiers: 'i' }
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册