提交 a7190d61 编写于 作者: C Christof Marti

Use 'dir' on Windows

上级 e089ce78
......@@ -26,6 +26,7 @@ import {IRawFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine} fro
enum Traversal {
Node = 1,
MacFind,
WindowsDir
}
interface IDirectoryEntry {
......@@ -76,7 +77,6 @@ export class FileWalker {
this.errors = [];
if (this.filePattern) {
this.filePattern = this.filePattern.replace(/\\/g, '/'); // Normalize file patterns to forward slashes
this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase();
}
}
......@@ -98,8 +98,7 @@ export class FileWalker {
if (exists) {
this.resultCount++;
onResult({
absolutePath: this.filePattern,
pathLabel: this.filePattern,
path: this.filePattern,
size
});
......@@ -117,21 +116,31 @@ export class FileWalker {
}
// File: Check for match on file pattern and include pattern
this.matchFile(onResult, extraFilePath, extraFilePath /* no workspace relative path */);
this.matchFile(onResult, null, extraFilePath /* no workspace relative path */);
});
}
let traverse = this.nodeJSTraversal;
if (!this.maxFilesize && platform.isMacintosh) {
this.traversal = Traversal.MacFind;
traverse = this.macFindTraversal;
if (!this.maxFilesize) {
if (platform.isMacintosh) {
this.traversal = Traversal.MacFind;
traverse = this.macFindTraversal;
} else if (platform.isWindows) {
this.traversal = Traversal.WindowsDir;
traverse = this.windowsDirTraversal;
}
}
const isNodeTraversal = traverse === this.nodeJSTraversal;
if (!isNodeTraversal) {
this.cmdForkStartTime = Date.now();
}
// For each root folder
flow.parallel(rootFolders, (rootFolder, rootFolderDone: (err?: Error) => void) => {
traverse.call(this, rootFolder, onResult, err => {
if (err) {
if (traverse === this.nodeJSTraversal) {
if (isNodeTraversal) {
rootFolderDone(err);
} else {
// fallback
......@@ -149,9 +158,8 @@ export class FileWalker {
}
private macFindTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void {
this.cmdForkStartTime = Date.now();
const cmd = childProcess.spawn('find', ['-L', '.', '-type', 'f'], { cwd: rootFolder });
this.readStdout(cmd, (err: Error, stdout?: string) => {
this.readStdout(cmd, 'utf8', (err: Error, stdout?: string) => {
if (err) {
done(err);
return;
......@@ -166,23 +174,38 @@ export class FileWalker {
relativeFiles.pop();
}
this.cmdResultCount = relativeFiles.length;
this.matchFiles(rootFolder, relativeFiles, onResult);
// Support relative paths to files from a root resource (ignores excludes)
if (relativeFiles.indexOf(this.filePattern) !== -1) {
this.matchFile(onResult, [rootFolder, this.filePattern].join(paths.sep), this.filePattern);
done();
});
}
private windowsDirTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void {
const cmd = childProcess.spawn('cmd', ['/U', '/c', 'dir', '/s', '/b', '/a-d'], { cwd: rootFolder });
this.readStdout(cmd, 'ucs2', (err: Error, stdout?: string) => {
if (err) {
done(err);
return;
}
const tree = this.buildDirectoryTree(relativeFiles);
this.matchDirectoryTree(rootFolder, tree, onResult);
const relativeFiles = stdout.split(`\r\n${rootFolder}\\`);
relativeFiles[0] = relativeFiles[0].trim().substr(rootFolder.length + 1);
const n = relativeFiles.length;
relativeFiles[n - 1] = relativeFiles[n - 1].trim();
if (!relativeFiles[n - 1]) {
relativeFiles.pop();
}
this.matchFiles(rootFolder, relativeFiles, onResult);
done();
});
}
private readStdout(cmd: childProcess.ChildProcess, cb: (err: Error, stdout?: string) => void): void {
private readStdout(cmd: childProcess.ChildProcess, encoding: string, cb: (err: Error, stdout?: string) => void): void {
let done = (err: Error, stdout?: string) => {
done = () => {};
this.cmdForkResultTime = Date.now();
cb(err, stdout);
};
......@@ -195,10 +218,9 @@ export class FileWalker {
cmd.on('close', code => {
if (code !== 0) {
done(new Error(`find failed with error code ${code}: ${this.decodeData(stderr)}`));
done(new Error(`find failed with error code ${code}: ${this.decodeData(stderr, encoding)}`));
} else {
this.cmdForkResultTime = Date.now();
done(null, this.decodeData(stdout));
done(null, this.decodeData(stdout, encoding));
}
});
}
......@@ -211,10 +233,21 @@ export class FileWalker {
return buffers;
}
private decodeData(buffers: Buffer[]): string {
const decoder = new StringDecoder('utf8');
return buffers.map(data => decoder.write(data))
.reduce((all, current) => all + current, '');
private decodeData(buffers: Buffer[], encoding: string): string {
const decoder = new StringDecoder(encoding);
return buffers.map(buffer => decoder.write(buffer)).join('');
}
private matchFiles(rootFolder: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) {
this.cmdResultCount = relativeFiles.length;
// Support relative paths to files from a root resource (ignores excludes)
if (relativeFiles.indexOf(this.filePattern) !== -1) {
this.matchFile(onResult, rootFolder, this.filePattern);
}
const tree = this.buildDirectoryTree(relativeFiles);
this.matchDirectoryTree(rootFolder, tree, onResult);
}
private buildDirectoryTree(relativeFilePaths: string[]): IDirectoryTree {
......@@ -248,7 +281,7 @@ export class FileWalker {
self.directoriesWalked++;
for (let i = 0, n = entries.length; i < n; i++) {
const entry = entries[i];
const relativePath = entry.relativePath; // assumes slashes as separator
const relativePath = entry.relativePath;
// Check exclude pattern
// If the user searches for the exact file name, we adjust the glob matching
......@@ -267,22 +300,22 @@ export class FileWalker {
continue; // ignore file if its path matches with the file pattern because that is already matched above
}
self.matchFile(onResult, [rootFolder, relativePath].join(paths.sep), relativePath);
self.matchFile(onResult, rootFolder, relativePath);
}
};
}
matchDirectory(rootEntries);
}
private nodeJSTraversal(absolutePath: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void {
private nodeJSTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void {
this.directoriesWalked++;
extfs.readdir(absolutePath, (error: Error, files: string[]) => {
extfs.readdir(rootFolder, (error: Error, files: string[]) => {
if (error || this.isCanceled || this.isLimitHit) {
return done();
}
// Support relative paths to files from a root resource (ignores excludes)
return this.checkFilePatternRelativeMatch(absolutePath, (match, size) => {
return this.checkFilePatternRelativeMatch(rootFolder, (match, size) => {
if (this.isCanceled || this.isLimitHit) {
return done();
}
......@@ -291,13 +324,13 @@ export class FileWalker {
if (match) {
this.resultCount++;
onResult({
absolutePath: match,
pathLabel: this.filePattern,
base: rootFolder,
path: this.filePattern,
size
});
}
return this.doWalk(paths.normalize(absolutePath), '', files, onResult, done);
return this.doWalk(rootFolder, '', files, onResult, done);
});
});
}
......@@ -340,7 +373,7 @@ export class FileWalker {
});
}
private doWalk(absolutePath: string, relativeParentPathWithSlashes: string, files: string[], onResult: (result: IRawFileMatch) => void, done: (error: Error) => void): void {
private doWalk(rootFolder: string, relativeParentPath: string, files: string[], onResult: (result: IRawFileMatch) => void, done: (error: Error) => void): void {
// Execute tasks on each file in parallel to optimize throughput
flow.parallel(files, (file: string, clb: (error: Error) => void): void => {
......@@ -359,13 +392,13 @@ export class FileWalker {
}
// Check exclude pattern
let currentRelativePathWithSlashes = relativeParentPathWithSlashes ? [relativeParentPathWithSlashes, file].join('/') : file;
if (this.excludePattern(currentRelativePathWithSlashes, () => siblings)) {
let currentRelativePath = relativeParentPath ? [relativeParentPath, file].join(paths.sep) : file;
if (this.excludePattern(currentRelativePath, () => siblings)) {
return clb(null);
}
// Use lstat to detect links
let currentAbsolutePath = [absolutePath, file].join(paths.sep);
let currentAbsolutePath = [rootFolder, currentRelativePath].join(paths.sep);
fs.lstat(currentAbsolutePath, (error, lstat) => {
if (error || this.isCanceled || this.isLimitHit) {
return clb(null);
......@@ -401,7 +434,7 @@ export class FileWalker {
return clb(null);
}
this.doWalk(currentAbsolutePath, currentRelativePathWithSlashes, children, onResult, clb);
this.doWalk(rootFolder, currentRelativePath, children, onResult, clb);
});
});
}
......@@ -409,7 +442,7 @@ export class FileWalker {
// File: Check for match on file pattern and include pattern
else {
this.filesWalked++;
if (currentRelativePathWithSlashes === this.filePattern) {
if (currentRelativePath === this.filePattern) {
return clb(null); // ignore file if its path matches with the file pattern because checkFilePatternRelativeMatch() takes care of those
}
......@@ -417,7 +450,7 @@ export class FileWalker {
return clb(null); // ignore file if max file size is hit
}
this.matchFile(onResult, currentAbsolutePath, currentRelativePathWithSlashes, stat.size);
this.matchFile(onResult, rootFolder, currentRelativePath, stat.size);
}
// Unwind
......@@ -433,8 +466,8 @@ export class FileWalker {
});
}
private matchFile(onResult: (result: IRawFileMatch) => void, absolutePath: string, relativePathWithSlashes: string, size?: number): void {
if (this.isFilePatternMatch(relativePathWithSlashes) && (!this.includePattern || this.includePattern(relativePathWithSlashes))) {
private matchFile(onResult: (result: IRawFileMatch) => void, base: string, path: string, size?: number): void {
if (this.isFilePatternMatch(path) && (!this.includePattern || this.includePattern(path))) {
this.resultCount++;
if (this.maxResults && this.resultCount > this.maxResults) {
......@@ -443,8 +476,8 @@ export class FileWalker {
if (!this.isLimitHit) {
onResult({
absolutePath,
pathLabel: relativePathWithSlashes,
base,
path,
size
});
}
......
......@@ -67,9 +67,9 @@ export class SearchService implements IRawSearchService {
searchPromise = this.doSearch(engine, batchSize)
.then(c, e, progress => {
if (Array.isArray(progress)) {
p(progress.map(m => ({ path: m.absolutePath })));
} else if ((<IRawFileMatch>progress).absolutePath) {
p({ path: (<IRawFileMatch>progress).absolutePath });
p(progress.map(m => this.rawMatchToSearchItem(m)));
} else if ((<IRawFileMatch>progress).path) {
p(this.rawMatchToSearchItem(<IRawFileMatch>progress));
} else {
p(progress);
}
......@@ -77,6 +77,10 @@ export class SearchService implements IRawSearchService {
}, () => searchPromise.cancel());
}
private rawMatchToSearchItem(match: IRawFileMatch): ISerializedFileMatch {
return { path: match.base ? [match.base, match.path].join(paths.nativeSep) : match.path };
}
private doSortedSearch(engine: ISearchEngine<IRawFileMatch>, config: IRawSearch, batchSize?: number): PPromise<ISerializedSearchComplete, IRawProgressItem<IRawFileMatch>> {
let searchPromise;
return new PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>((c, e, p) => {
......@@ -178,7 +182,7 @@ export class SearchService implements IRawSearchService {
const normalizedSearchValue = strings.stripWildcards(filePattern).toLowerCase();
const compare = (elementA: IRawFileMatch, elementB: IRawFileMatch) => compareByScore(elementA, elementB, FileMatchAccessor, filePattern, normalizedSearchValue, cache.scorerCache);
const filteredWrappers = arrays.top(results, compare, config.maxResults);
return filteredWrappers.map(result => ({ path: result.absolutePath }));
return filteredWrappers.map(result => this.rawMatchToSearchItem(result));
}
private sendProgress(results: ISerializedFileMatch[], progressCb: (batch: ISerializedFileMatch[]) => void, batchSize?: number) {
......@@ -218,13 +222,12 @@ export class SearchService implements IRawSearchService {
// Pattern match on results and adjust highlights
let results: IRawFileMatch[] = [];
const normalizedSearchValue = searchValue.replace(/\\/g, '/'); // Normalize file patterns to forward slashes
const normalizedSearchValueLowercase = strings.stripWildcards(normalizedSearchValue).toLowerCase();
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
for (let i = 0; i < cachedEntries.length; i++) {
let entry = cachedEntries[i];
// Check if this entry is a match for the search value
if (!scorer.matches(entry.pathLabel, normalizedSearchValueLowercase)) {
if (!scorer.matches(entry.path, normalizedSearchValueLowercase)) {
continue;
}
......@@ -285,12 +288,12 @@ class FileMatchAccessor {
public static getLabel(match: IFileMatch): string {
if (!match.label) {
match.label = paths.basename(match.absolutePath);
match.label = paths.basename(match.path);
}
return match.label;
}
public static getResourcePath(match: IFileMatch): string {
return match.absolutePath;
return match.path;
}
}
......@@ -30,9 +30,9 @@ export interface IRawSearchService {
}
export interface IRawFileMatch {
absolutePath: string;
pathLabel: string;
size: number;
base?: string;
path: string;
size?: number;
}
export interface ISearchEngine<T> {
......
......@@ -5,11 +5,12 @@
'use strict';
import strings = require('vs/base/common/strings');
import * as strings from 'vs/base/common/strings';
import fs = require('fs');
import * as fs from 'fs';
import * as path from 'path';
import baseMime = require('vs/base/common/mime');
import * as baseMime from 'vs/base/common/mime';
import {ILineMatch, IProgress} from 'vs/platform/search/common/search';
import {detectMimeAndEncodingFromBuffer} from 'vs/base/node/mime';
import {FileWalker} from 'vs/workbench/services/search/node/fileSearch';
......@@ -110,6 +111,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
return unwind(size);
};
const absolutePath = result.base ? [result.base, result.path].join(path.sep) : result.path;
let perLineCallback = (line: string, lineNumber: number) => {
if (this.limitReached || this.isCanceled) {
return; // return early if canceled or limit reached
......@@ -126,7 +128,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
}
if (fileMatch === null) {
fileMatch = new FileMatch(result.absolutePath);
fileMatch = new FileMatch(absolutePath);
}
if (lineMatch === null) {
......@@ -141,7 +143,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
};
// Read lines buffered to support large files
this.readlinesAsync(result.absolutePath, perLineCallback, { bufferLength: 8096, encoding: this.fileEncoding }, doneCallback);
this.readlinesAsync(absolutePath, perLineCallback, { bufferLength: 8096, encoding: this.fileEncoding }, doneCallback);
}, (error, isLimitHit) => {
this.walkerIsDone = true;
this.walkerError = error;
......
......@@ -159,7 +159,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.strictEqual(path.basename(res.absolutePath), 'site.less');
assert.strictEqual(path.basename(res.path), 'site.less');
done();
});
});
......@@ -256,7 +256,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.equal(path.basename(res.absolutePath), '汉语.txt');
assert.equal(path.basename(res.path), '汉语.txt');
done();
});
});
......@@ -296,7 +296,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.equal(path.basename(res.absolutePath), 'site.css');
assert.equal(path.basename(res.path), 'site.css');
done();
});
});
......@@ -317,7 +317,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.equal(path.basename(res.absolutePath), 'company.js');
assert.equal(path.basename(res.path), 'company.js');
done();
});
});
......@@ -339,7 +339,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.equal(path.basename(res.absolutePath), 'company.js');
assert.equal(path.basename(res.path), 'company.js');
done();
});
});
......@@ -365,7 +365,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.equal(path.basename(res.absolutePath), 'company.js');
assert.equal(path.basename(res.path), 'company.js');
done();
});
});
......@@ -392,7 +392,7 @@ suite('Search', () => {
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
assert.equal(path.basename(res.absolutePath), 'site.css');
assert.equal(path.basename(res.path), 'site.css');
done();
});
});
......
......@@ -5,7 +5,8 @@
'use strict';
import assert = require('assert');
import * as assert from 'assert';
import {normalize} from 'path';
import {IProgress, IUncachedSearchStats} from 'vs/platform/search/common/search';
import {ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchComplete} from 'vs/workbench/services/search/node/search';
......@@ -67,18 +68,18 @@ class TestSearchEngine implements ISearchEngine<IRawFileMatch> {
suite('SearchService', () => {
const rawSearch: IRawSearch = {
rootFolders: ['/some/where'],
rootFolders: [normalize('/some/where')],
filePattern: 'a'
};
const rawMatch: IRawFileMatch = {
absolutePath: '/some/where',
pathLabel: 'where',
base: normalize('/some'),
path: 'where',
size: 123
};
const match: ISerializedFileMatch = {
path: '/some/where'
path: normalize('/some/where')
};
test('Individual results', function () {
......@@ -122,7 +123,7 @@ suite('SearchService', () => {
});
test('Collect batched results', function () {
const path = '/some/where';
const uriPath = '/some/where';
let i = 25;
const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch);
const service = new RawSearchService();
......@@ -134,16 +135,16 @@ suite('SearchService', () => {
assert.strictEqual(result.results.length, 25, 'Result');
assert.strictEqual(progressResults.length, 25, 'Progress');
}, null, match => {
assert.strictEqual(match.resource.path, path);
assert.strictEqual(match.resource.path, uriPath);
progressResults.push(match);
});
});
test('Sorted results', function () {
const paths = ['bab', 'bbc', 'abb'];
const matches = paths.map(path => ({
absolutePath: `/some/where/${path}`,
pathLabel: path,
const matches: IRawFileMatch[] = paths.map(path => ({
base: normalize('/some/where'),
path,
size: 3
}));
let i = 0;
......@@ -152,13 +153,13 @@ suite('SearchService', () => {
const results = [];
return service.doFileSearch(Engine, {
rootFolders: ['/some/where'],
rootFolders: [normalize('/some/where')],
filePattern: 'bb',
sortByScore: true,
maxResults: 2
}, 1).then(() => {
assert.notStrictEqual(typeof TestSearchEngine.last.config.maxResults, 'number');
assert.deepStrictEqual(results, ['/some/where/bbc', '/some/where/bab']);
assert.deepStrictEqual(results, [normalize('/some/where/bbc'), normalize('/some/where/bab')]);
}, null, value => {
if (Array.isArray(value)) {
results.push(...value.map(v => v.path));
......@@ -175,7 +176,7 @@ suite('SearchService', () => {
const results = [];
return service.doFileSearch(Engine, {
rootFolders: ['/some/where'],
rootFolders: [normalize('/some/where')],
filePattern: 'a',
sortByScore: true,
maxResults: 23
......@@ -196,9 +197,9 @@ suite('SearchService', () => {
test('Cached results', function () {
const paths = ['bcb', 'bbc', 'aab'];
const matches = paths.map(path => ({
absolutePath: `/some/where/${path}`,
pathLabel: path,
const matches: IRawFileMatch[] = paths.map(path => ({
base: normalize('/some/where'),
path,
size: 3
}));
let i = 0;
......@@ -207,13 +208,13 @@ suite('SearchService', () => {
const results = [];
return service.doFileSearch(Engine, {
rootFolders: ['/some/where'],
rootFolders: [normalize('/some/where')],
filePattern: 'b',
sortByScore: true,
cacheKey: 'x'
}, -1).then(complete => {
assert.strictEqual(complete.stats.fromCache, false);
assert.deepStrictEqual(results, ['/some/where/bcb', '/some/where/bbc', '/some/where/aab']);
assert.deepStrictEqual(results, [normalize('/some/where/bcb'), normalize('/some/where/bbc'), normalize('/some/where/aab')]);
}, null, value => {
if (Array.isArray(value)) {
results.push(...value.map(v => v.path));
......@@ -223,13 +224,13 @@ suite('SearchService', () => {
}).then(() => {
const results = [];
return service.doFileSearch(Engine, {
rootFolders: ['/some/where'],
rootFolders: [normalize('/some/where')],
filePattern: 'bc',
sortByScore: true,
cacheKey: 'x'
}, -1).then(complete => {
assert.ok(complete.stats.fromCache);
assert.deepStrictEqual(results, ['/some/where/bcb', '/some/where/bbc']);
assert.deepStrictEqual(results, [normalize('/some/where/bcb'), normalize('/some/where/bbc')]);
}, null, value => {
if (Array.isArray(value)) {
results.push(...value.map(v => v.path));
......@@ -241,19 +242,19 @@ suite('SearchService', () => {
return service.clearCache('x');
}).then(() => {
matches.push({
absolutePath: '/some/where/bc',
pathLabel: 'bc',
base: normalize('/some/where'),
path: 'bc',
size: 3
});
const results = [];
return service.doFileSearch(Engine, {
rootFolders: ['/some/where'],
rootFolders: [normalize('/some/where')],
filePattern: 'bc',
sortByScore: true,
cacheKey: 'x'
}, -1).then(complete => {
assert.strictEqual(complete.stats.fromCache, false);
assert.deepStrictEqual(results, ['/some/where/bc']);
assert.deepStrictEqual(results, [normalize('/some/where/bc')]);
}, null, value => {
if (Array.isArray(value)) {
results.push(...value.map(v => v.path));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册