multi root chockidar

This commit is contained in:
Martin Aeschlimann
2018-05-22 17:09:18 +02:00
parent 35be70dfcb
commit 44aa6c8aac
8 changed files with 1057 additions and 483 deletions

View File

@@ -8,7 +8,7 @@ declare module 'vscode-chokidar' {
/**
* takes paths to be watched recursively and options
*/
export function watch(paths: string, options: IOptions): FSWatcher;
export function watch(paths: string | string[], options: IOptions): FSWatcher;
export interface IOptions {

View File

@@ -63,350 +63,369 @@ suite('Glob', () => {
// console.profileEnd();
// });
function assertGlobMatch(pattern: string | glob.IRelativePattern, input: string) {
assert(glob.match(pattern, input), `${pattern} should match ${input}`);
}
function assertNoGlobMatch(pattern: string | glob.IRelativePattern, input: string) {
assert(!glob.match(pattern, input), `${pattern} should not match ${input}`);
}
test('simple', function () {
let p = 'node_modules';
assert(glob.match(p, 'node_modules'));
assert(!glob.match(p, 'node_module'));
assert(!glob.match(p, '/node_modules'));
assert(!glob.match(p, 'test/node_modules'));
assertGlobMatch(p, 'node_modules');
assertNoGlobMatch(p, 'node_module');
assertNoGlobMatch(p, '/node_modules');
assertNoGlobMatch(p, 'test/node_modules');
p = 'test.txt';
assert(glob.match(p, 'test.txt'));
assert(!glob.match(p, 'test?txt'));
assert(!glob.match(p, '/text.txt'));
assert(!glob.match(p, 'test/test.txt'));
assertGlobMatch(p, 'test.txt');
assertNoGlobMatch(p, 'test?txt');
assertNoGlobMatch(p, '/text.txt');
assertNoGlobMatch(p, 'test/test.txt');
p = 'test(.txt';
assert(glob.match(p, 'test(.txt'));
assert(!glob.match(p, 'test?txt'));
assertGlobMatch(p, 'test(.txt');
assertNoGlobMatch(p, 'test?txt');
p = 'qunit';
assert(glob.match(p, 'qunit'));
assert(!glob.match(p, 'qunit.css'));
assert(!glob.match(p, 'test/qunit'));
assertGlobMatch(p, 'qunit');
assertNoGlobMatch(p, 'qunit.css');
assertNoGlobMatch(p, 'test/qunit');
// Absolute
p = '/DNXConsoleApp/**/*.cs';
assert(glob.match(p, '/DNXConsoleApp/Program.cs'));
assert(glob.match(p, '/DNXConsoleApp/foo/Program.cs'));
assertGlobMatch(p, '/DNXConsoleApp/Program.cs');
assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs');
p = 'C:/DNXConsoleApp/**/*.cs';
assert(glob.match(p, 'C:\\DNXConsoleApp\\Program.cs'));
assert(glob.match(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'));
assertGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs');
assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs');
});
test('dot hidden', function () {
let p = '.*';
assert(glob.match(p, '.git'));
assert(glob.match(p, '.hidden.txt'));
assert(!glob.match(p, 'git'));
assert(!glob.match(p, 'hidden.txt'));
assert(!glob.match(p, 'path/.git'));
assert(!glob.match(p, 'path/.hidden.txt'));
assertGlobMatch(p, '.git');
assertGlobMatch(p, '.hidden.txt');
assertNoGlobMatch(p, 'git');
assertNoGlobMatch(p, 'hidden.txt');
assertNoGlobMatch(p, 'path/.git');
assertNoGlobMatch(p, 'path/.hidden.txt');
p = '**/.*';
assert(glob.match(p, '.git'));
assert(glob.match(p, '.hidden.txt'));
assert(!glob.match(p, 'git'));
assert(!glob.match(p, 'hidden.txt'));
assert(glob.match(p, 'path/.git'));
assert(glob.match(p, 'path/.hidden.txt'));
assert(!glob.match(p, 'path/git'));
assert(!glob.match(p, 'pat.h/hidden.txt'));
assertGlobMatch(p, '.git');
assertGlobMatch(p, '.hidden.txt');
assertNoGlobMatch(p, 'git');
assertNoGlobMatch(p, 'hidden.txt');
assertGlobMatch(p, 'path/.git');
assertGlobMatch(p, 'path/.hidden.txt');
assertNoGlobMatch(p, 'path/git');
assertNoGlobMatch(p, 'pat.h/hidden.txt');
p = '._*';
assert(glob.match(p, '._git'));
assert(glob.match(p, '._hidden.txt'));
assert(!glob.match(p, 'git'));
assert(!glob.match(p, 'hidden.txt'));
assert(!glob.match(p, 'path/._git'));
assert(!glob.match(p, 'path/._hidden.txt'));
assertGlobMatch(p, '._git');
assertGlobMatch(p, '._hidden.txt');
assertNoGlobMatch(p, 'git');
assertNoGlobMatch(p, 'hidden.txt');
assertNoGlobMatch(p, 'path/._git');
assertNoGlobMatch(p, 'path/._hidden.txt');
p = '**/._*';
assert(glob.match(p, '._git'));
assert(glob.match(p, '._hidden.txt'));
assert(!glob.match(p, 'git'));
assert(!glob.match(p, 'hidden._txt'));
assert(glob.match(p, 'path/._git'));
assert(glob.match(p, 'path/._hidden.txt'));
assert(!glob.match(p, 'path/git'));
assert(!glob.match(p, 'pat.h/hidden._txt'));
assertGlobMatch(p, '._git');
assertGlobMatch(p, '._hidden.txt');
assertNoGlobMatch(p, 'git');
assertNoGlobMatch(p, 'hidden._txt');
assertGlobMatch(p, 'path/._git');
assertGlobMatch(p, 'path/._hidden.txt');
assertNoGlobMatch(p, 'path/git');
assertNoGlobMatch(p, 'pat.h/hidden._txt');
});
test('file pattern', function () {
let p = '*.js';
assert(glob.match(p, 'foo.js'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_modules/foo.js'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assertGlobMatch(p, 'foo.js');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_modules/foo.js');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
p = 'html.*';
assert(glob.match(p, 'html.js'));
assert(glob.match(p, 'html.txt'));
assert(!glob.match(p, 'htm.txt'));
assertGlobMatch(p, 'html.js');
assertGlobMatch(p, 'html.txt');
assertNoGlobMatch(p, 'htm.txt');
p = '*.*';
assert(glob.match(p, 'html.js'));
assert(glob.match(p, 'html.txt'));
assert(glob.match(p, 'htm.txt'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_modules/foo.js'));
assertGlobMatch(p, 'html.js');
assertGlobMatch(p, 'html.txt');
assertGlobMatch(p, 'htm.txt');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_modules/foo.js');
p = 'node_modules/test/*.js';
assert(glob.match(p, 'node_modules/test/foo.js'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_module/test/foo.js'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assertGlobMatch(p, 'node_modules/test/foo.js');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_module/test/foo.js');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
});
test('star', function () {
let p = 'node*modules';
assert(glob.match(p, 'node_modules'));
assert(glob.match(p, 'node_super_modules'));
assert(!glob.match(p, 'node_module'));
assert(!glob.match(p, '/node_modules'));
assert(!glob.match(p, 'test/node_modules'));
assertGlobMatch(p, 'node_modules');
assertGlobMatch(p, 'node_super_modules');
assertNoGlobMatch(p, 'node_module');
assertNoGlobMatch(p, '/node_modules');
assertNoGlobMatch(p, 'test/node_modules');
p = '*';
assert(glob.match(p, 'html.js'));
assert(glob.match(p, 'html.txt'));
assert(glob.match(p, 'htm.txt'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_modules/foo.js'));
assertGlobMatch(p, 'html.js');
assertGlobMatch(p, 'html.txt');
assertGlobMatch(p, 'htm.txt');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_modules/foo.js');
});
test('file / folder match', function () {
let p = '**/node_modules/**';
assertGlobMatch(p, 'node_modules');
assertGlobMatch(p, 'node_modules/');
assertGlobMatch(p, 'a/node_modules');
assertGlobMatch(p, 'a/node_modules/');
assertGlobMatch(p, 'node_modules/foo');
assertGlobMatch(p, 'foo/node_modules/foo/bar');
});
test('questionmark', function () {
let p = 'node?modules';
assert(glob.match(p, 'node_modules'));
assert(!glob.match(p, 'node_super_modules'));
assert(!glob.match(p, 'node_module'));
assert(!glob.match(p, '/node_modules'));
assert(!glob.match(p, 'test/node_modules'));
assertGlobMatch(p, 'node_modules');
assertNoGlobMatch(p, 'node_super_modules');
assertNoGlobMatch(p, 'node_module');
assertNoGlobMatch(p, '/node_modules');
assertNoGlobMatch(p, 'test/node_modules');
p = '?';
assert(glob.match(p, 'h'));
assert(!glob.match(p, 'html.txt'));
assert(!glob.match(p, 'htm.txt'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_modules/foo.js'));
assertGlobMatch(p, 'h');
assertNoGlobMatch(p, 'html.txt');
assertNoGlobMatch(p, 'htm.txt');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_modules/foo.js');
});
test('globstar', function () {
let p = '**/*.js';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'folder/foo.js'));
assert(glob.match(p, '/node_modules/foo.js'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assert(!glob.match(p, '/some.js/test'));
assert(!glob.match(p, '\\some.js\\test'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'folder/foo.js');
assertGlobMatch(p, '/node_modules/foo.js');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
assertNoGlobMatch(p, '/some.js/test');
assertNoGlobMatch(p, '\\some.js\\test');
p = '**/project.json';
assert(glob.match(p, 'project.json'));
assert(glob.match(p, '/project.json'));
assert(glob.match(p, 'some/folder/project.json'));
assert(!glob.match(p, 'some/folder/file_project.json'));
assert(!glob.match(p, 'some/folder/fileproject.json'));
// assert(!glob.match(p, '/rrproject.json')); TODO@ben this still fails if T1-3 are disabled
assert(!glob.match(p, 'some/rrproject.json'));
// assert(!glob.match(p, 'rrproject.json'));
// assert(!glob.match(p, '\\rrproject.json'));
assert(!glob.match(p, 'some\\rrproject.json'));
assertGlobMatch(p, 'project.json');
assertGlobMatch(p, '/project.json');
assertGlobMatch(p, 'some/folder/project.json');
assertNoGlobMatch(p, 'some/folder/file_project.json');
assertNoGlobMatch(p, 'some/folder/fileproject.json');
// assertNoGlobMatch(p, '/rrproject.json'); TODO@ben this still fails if T1-3 are disabled
assertNoGlobMatch(p, 'some/rrproject.json');
// assertNoGlobMatch(p, 'rrproject.json');
// assertNoGlobMatch(p, '\\rrproject.json');
assertNoGlobMatch(p, 'some\\rrproject.json');
p = 'test/**';
assert(glob.match(p, 'test'));
assert(glob.match(p, 'test/foo.js'));
assert(glob.match(p, 'test/other/foo.js'));
assert(!glob.match(p, 'est/other/foo.js'));
assertGlobMatch(p, 'test');
assertGlobMatch(p, 'test/foo.js');
assertGlobMatch(p, 'test/other/foo.js');
assertNoGlobMatch(p, 'est/other/foo.js');
p = '**';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'folder/foo.js'));
assert(glob.match(p, '/node_modules/foo.js'));
assert(glob.match(p, 'foo.jss'));
assert(glob.match(p, 'some.js/test'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'folder/foo.js');
assertGlobMatch(p, '/node_modules/foo.js');
assertGlobMatch(p, 'foo.jss');
assertGlobMatch(p, 'some.js/test');
p = 'test/**/*.js';
assert(glob.match(p, 'test/foo.js'));
assert(glob.match(p, 'test/other/foo.js'));
assert(glob.match(p, 'test/other/more/foo.js'));
assert(!glob.match(p, 'test/foo.ts'));
assert(!glob.match(p, 'test/other/foo.ts'));
assert(!glob.match(p, 'test/other/more/foo.ts'));
assertGlobMatch(p, 'test/foo.js');
assertGlobMatch(p, 'test/other/foo.js');
assertGlobMatch(p, 'test/other/more/foo.js');
assertNoGlobMatch(p, 'test/foo.ts');
assertNoGlobMatch(p, 'test/other/foo.ts');
assertNoGlobMatch(p, 'test/other/more/foo.ts');
p = '**/**/*.js';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'folder/foo.js'));
assert(glob.match(p, '/node_modules/foo.js'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'folder/foo.js');
assertGlobMatch(p, '/node_modules/foo.js');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
p = '**/node_modules/**/*.js';
assert(!glob.match(p, 'foo.js'));
assert(!glob.match(p, 'folder/foo.js'));
assert(glob.match(p, 'node_modules/foo.js'));
assert(glob.match(p, 'node_modules/some/folder/foo.js'));
assert(!glob.match(p, 'node_modules/some/folder/foo.ts'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assertNoGlobMatch(p, 'foo.js');
assertNoGlobMatch(p, 'folder/foo.js');
assertGlobMatch(p, 'node_modules/foo.js');
assertGlobMatch(p, 'node_modules/some/folder/foo.js');
assertNoGlobMatch(p, 'node_modules/some/folder/foo.ts');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
p = '{**/node_modules/**,**/.git/**,**/bower_components/**}';
assert(glob.match(p, 'node_modules'));
assert(glob.match(p, '/node_modules'));
assert(glob.match(p, '/node_modules/more'));
assert(glob.match(p, 'some/test/node_modules'));
assert(glob.match(p, 'some\\test\\node_modules'));
assert(glob.match(p, 'C:\\\\some\\test\\node_modules'));
assert(glob.match(p, 'C:\\\\some\\test\\node_modules\\more'));
assertGlobMatch(p, 'node_modules');
assertGlobMatch(p, '/node_modules');
assertGlobMatch(p, '/node_modules/more');
assertGlobMatch(p, 'some/test/node_modules');
assertGlobMatch(p, 'some\\test\\node_modules');
assertGlobMatch(p, 'C:\\\\some\\test\\node_modules');
assertGlobMatch(p, 'C:\\\\some\\test\\node_modules\\more');
assert(glob.match(p, 'bower_components'));
assert(glob.match(p, 'bower_components/more'));
assert(glob.match(p, '/bower_components'));
assert(glob.match(p, 'some/test/bower_components'));
assert(glob.match(p, 'some\\test\\bower_components'));
assert(glob.match(p, 'C:\\\\some\\test\\bower_components'));
assert(glob.match(p, 'C:\\\\some\\test\\bower_components\\more'));
assertGlobMatch(p, 'bower_components');
assertGlobMatch(p, 'bower_components/more');
assertGlobMatch(p, '/bower_components');
assertGlobMatch(p, 'some/test/bower_components');
assertGlobMatch(p, 'some\\test\\bower_components');
assertGlobMatch(p, 'C:\\\\some\\test\\bower_components');
assertGlobMatch(p, 'C:\\\\some\\test\\bower_components\\more');
assert(glob.match(p, '.git'));
assert(glob.match(p, '/.git'));
assert(glob.match(p, 'some/test/.git'));
assert(glob.match(p, 'some\\test\\.git'));
assert(glob.match(p, 'C:\\\\some\\test\\.git'));
assertGlobMatch(p, '.git');
assertGlobMatch(p, '/.git');
assertGlobMatch(p, 'some/test/.git');
assertGlobMatch(p, 'some\\test\\.git');
assertGlobMatch(p, 'C:\\\\some\\test\\.git');
assert(!glob.match(p, 'tempting'));
assert(!glob.match(p, '/tempting'));
assert(!glob.match(p, 'some/test/tempting'));
assert(!glob.match(p, 'some\\test\\tempting'));
assert(!glob.match(p, 'C:\\\\some\\test\\tempting'));
assertNoGlobMatch(p, 'tempting');
assertNoGlobMatch(p, '/tempting');
assertNoGlobMatch(p, 'some/test/tempting');
assertNoGlobMatch(p, 'some\\test\\tempting');
assertNoGlobMatch(p, 'C:\\\\some\\test\\tempting');
p = '{**/package.json,**/project.json}';
assert(glob.match(p, 'package.json'));
assert(glob.match(p, '/package.json'));
assert(!glob.match(p, 'xpackage.json'));
assert(!glob.match(p, '/xpackage.json'));
assertGlobMatch(p, 'package.json');
assertGlobMatch(p, '/package.json');
assertNoGlobMatch(p, 'xpackage.json');
assertNoGlobMatch(p, '/xpackage.json');
});
test('issue 41724', function () {
let p = 'some/**/*.js';
assert(glob.match(p, 'some/foo.js'));
assert(glob.match(p, 'some/folder/foo.js'));
assert(!glob.match(p, 'something/foo.js'));
assert(!glob.match(p, 'something/folder/foo.js'));
assertGlobMatch(p, 'some/foo.js');
assertGlobMatch(p, 'some/folder/foo.js');
assertNoGlobMatch(p, 'something/foo.js');
assertNoGlobMatch(p, 'something/folder/foo.js');
p = 'some/**/*';
assert(glob.match(p, 'some/foo.js'));
assert(glob.match(p, 'some/folder/foo.js'));
assert(!glob.match(p, 'something/foo.js'));
assert(!glob.match(p, 'something/folder/foo.js'));
assertGlobMatch(p, 'some/foo.js');
assertGlobMatch(p, 'some/folder/foo.js');
assertNoGlobMatch(p, 'something/foo.js');
assertNoGlobMatch(p, 'something/folder/foo.js');
});
test('brace expansion', function () {
let p = '*.{html,js}';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'foo.html'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_modules/foo.js'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'foo.html');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_modules/foo.js');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
p = '*.{html}';
assert(glob.match(p, 'foo.html'));
assert(!glob.match(p, 'foo.js'));
assert(!glob.match(p, 'folder/foo.js'));
assert(!glob.match(p, '/node_modules/foo.js'));
assert(!glob.match(p, 'foo.jss'));
assert(!glob.match(p, 'some.js/test'));
assertGlobMatch(p, 'foo.html');
assertNoGlobMatch(p, 'foo.js');
assertNoGlobMatch(p, 'folder/foo.js');
assertNoGlobMatch(p, '/node_modules/foo.js');
assertNoGlobMatch(p, 'foo.jss');
assertNoGlobMatch(p, 'some.js/test');
p = '{node_modules,testing}';
assert(glob.match(p, 'node_modules'));
assert(glob.match(p, 'testing'));
assert(!glob.match(p, 'node_module'));
assert(!glob.match(p, 'dtesting'));
assertGlobMatch(p, 'node_modules');
assertGlobMatch(p, 'testing');
assertNoGlobMatch(p, 'node_module');
assertNoGlobMatch(p, 'dtesting');
p = '**/{foo,bar}';
assert(glob.match(p, 'foo'));
assert(glob.match(p, 'bar'));
assert(glob.match(p, 'test/foo'));
assert(glob.match(p, 'test/bar'));
assert(glob.match(p, 'other/more/foo'));
assert(glob.match(p, 'other/more/bar'));
assertGlobMatch(p, 'foo');
assertGlobMatch(p, 'bar');
assertGlobMatch(p, 'test/foo');
assertGlobMatch(p, 'test/bar');
assertGlobMatch(p, 'other/more/foo');
assertGlobMatch(p, 'other/more/bar');
p = '{foo,bar}/**';
assert(glob.match(p, 'foo'));
assert(glob.match(p, 'bar'));
assert(glob.match(p, 'foo/test'));
assert(glob.match(p, 'bar/test'));
assert(glob.match(p, 'foo/other/more'));
assert(glob.match(p, 'bar/other/more'));
assertGlobMatch(p, 'foo');
assertGlobMatch(p, 'bar');
assertGlobMatch(p, 'foo/test');
assertGlobMatch(p, 'bar/test');
assertGlobMatch(p, 'foo/other/more');
assertGlobMatch(p, 'bar/other/more');
p = '{**/*.d.ts,**/*.js}';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'testing/foo.js'));
assert(glob.match(p, 'testing\\foo.js'));
assert(glob.match(p, '/testing/foo.js'));
assert(glob.match(p, '\\testing\\foo.js'));
assert(glob.match(p, 'C:\\testing\\foo.js'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'testing/foo.js');
assertGlobMatch(p, 'testing\\foo.js');
assertGlobMatch(p, '/testing/foo.js');
assertGlobMatch(p, '\\testing\\foo.js');
assertGlobMatch(p, 'C:\\testing\\foo.js');
assert(glob.match(p, 'foo.d.ts'));
assert(glob.match(p, 'testing/foo.d.ts'));
assert(glob.match(p, 'testing\\foo.d.ts'));
assert(glob.match(p, '/testing/foo.d.ts'));
assert(glob.match(p, '\\testing\\foo.d.ts'));
assert(glob.match(p, 'C:\\testing\\foo.d.ts'));
assertGlobMatch(p, 'foo.d.ts');
assertGlobMatch(p, 'testing/foo.d.ts');
assertGlobMatch(p, 'testing\\foo.d.ts');
assertGlobMatch(p, '/testing/foo.d.ts');
assertGlobMatch(p, '\\testing\\foo.d.ts');
assertGlobMatch(p, 'C:\\testing\\foo.d.ts');
assert(!glob.match(p, 'foo.d'));
assert(!glob.match(p, 'testing/foo.d'));
assert(!glob.match(p, 'testing\\foo.d'));
assert(!glob.match(p, '/testing/foo.d'));
assert(!glob.match(p, '\\testing\\foo.d'));
assert(!glob.match(p, 'C:\\testing\\foo.d'));
assertNoGlobMatch(p, 'foo.d');
assertNoGlobMatch(p, 'testing/foo.d');
assertNoGlobMatch(p, 'testing\\foo.d');
assertNoGlobMatch(p, '/testing/foo.d');
assertNoGlobMatch(p, '\\testing\\foo.d');
assertNoGlobMatch(p, 'C:\\testing\\foo.d');
p = '{**/*.d.ts,**/*.js,path/simple.jgs}';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'testing/foo.js'));
assert(glob.match(p, 'testing\\foo.js'));
assert(glob.match(p, '/testing/foo.js'));
assert(glob.match(p, 'path/simple.jgs'));
assert(!glob.match(p, '/path/simple.jgs'));
assert(glob.match(p, '\\testing\\foo.js'));
assert(glob.match(p, 'C:\\testing\\foo.js'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'testing/foo.js');
assertGlobMatch(p, 'testing\\foo.js');
assertGlobMatch(p, '/testing/foo.js');
assertGlobMatch(p, 'path/simple.jgs');
assertNoGlobMatch(p, '/path/simple.jgs');
assertGlobMatch(p, '\\testing\\foo.js');
assertGlobMatch(p, 'C:\\testing\\foo.js');
p = '{**/*.d.ts,**/*.js,foo.[0-9]}';
assert(glob.match(p, 'foo.5'));
assert(glob.match(p, 'foo.8'));
assert(!glob.match(p, 'bar.5'));
assert(!glob.match(p, 'foo.f'));
assert(glob.match(p, 'foo.js'));
assertGlobMatch(p, 'foo.5');
assertGlobMatch(p, 'foo.8');
assertNoGlobMatch(p, 'bar.5');
assertNoGlobMatch(p, 'foo.f');
assertGlobMatch(p, 'foo.js');
p = 'prefix/{**/*.d.ts,**/*.js,foo.[0-9]}';
assert(glob.match(p, 'prefix/foo.5'));
assert(glob.match(p, 'prefix/foo.8'));
assert(!glob.match(p, 'prefix/bar.5'));
assert(!glob.match(p, 'prefix/foo.f'));
assert(glob.match(p, 'prefix/foo.js'));
assertGlobMatch(p, 'prefix/foo.5');
assertGlobMatch(p, 'prefix/foo.8');
assertNoGlobMatch(p, 'prefix/bar.5');
assertNoGlobMatch(p, 'prefix/foo.f');
assertGlobMatch(p, 'prefix/foo.js');
});
test('expression support (single)', function () {
@@ -465,57 +484,57 @@ suite('Glob', () => {
test('brackets', function () {
let p = 'foo.[0-9]';
assert(glob.match(p, 'foo.5'));
assert(glob.match(p, 'foo.8'));
assert(!glob.match(p, 'bar.5'));
assert(!glob.match(p, 'foo.f'));
assertGlobMatch(p, 'foo.5');
assertGlobMatch(p, 'foo.8');
assertNoGlobMatch(p, 'bar.5');
assertNoGlobMatch(p, 'foo.f');
p = 'foo.[^0-9]';
assert(!glob.match(p, 'foo.5'));
assert(!glob.match(p, 'foo.8'));
assert(!glob.match(p, 'bar.5'));
assert(glob.match(p, 'foo.f'));
assertNoGlobMatch(p, 'foo.5');
assertNoGlobMatch(p, 'foo.8');
assertNoGlobMatch(p, 'bar.5');
assertGlobMatch(p, 'foo.f');
p = 'foo.[!0-9]';
assert(!glob.match(p, 'foo.5'));
assert(!glob.match(p, 'foo.8'));
assert(!glob.match(p, 'bar.5'));
assert(glob.match(p, 'foo.f'));
assertNoGlobMatch(p, 'foo.5');
assertNoGlobMatch(p, 'foo.8');
assertNoGlobMatch(p, 'bar.5');
assertGlobMatch(p, 'foo.f');
p = 'foo.[0!^*?]';
assert(!glob.match(p, 'foo.5'));
assert(!glob.match(p, 'foo.8'));
assert(glob.match(p, 'foo.0'));
assert(glob.match(p, 'foo.!'));
assert(glob.match(p, 'foo.^'));
assert(glob.match(p, 'foo.*'));
assert(glob.match(p, 'foo.?'));
assertNoGlobMatch(p, 'foo.5');
assertNoGlobMatch(p, 'foo.8');
assertGlobMatch(p, 'foo.0');
assertGlobMatch(p, 'foo.!');
assertGlobMatch(p, 'foo.^');
assertGlobMatch(p, 'foo.*');
assertGlobMatch(p, 'foo.?');
p = 'foo[/]bar';
assert(!glob.match(p, 'foo/bar'));
assertNoGlobMatch(p, 'foo/bar');
p = 'foo.[[]';
assert(glob.match(p, 'foo.['));
assertGlobMatch(p, 'foo.[');
p = 'foo.[]]';
assert(glob.match(p, 'foo.]'));
assertGlobMatch(p, 'foo.]');
p = 'foo.[][!]';
assert(glob.match(p, 'foo.]'));
assert(glob.match(p, 'foo.['));
assert(glob.match(p, 'foo.!'));
assertGlobMatch(p, 'foo.]');
assertGlobMatch(p, 'foo.[');
assertGlobMatch(p, 'foo.!');
p = 'foo.[]-]';
assert(glob.match(p, 'foo.]'));
assert(glob.match(p, 'foo.-'));
assertGlobMatch(p, 'foo.]');
assertGlobMatch(p, 'foo.-');
});
test('full path', function () {
@@ -527,111 +546,111 @@ suite('Glob', () => {
test('prefix agnostic', function () {
let p = '**/*.js';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, '/foo.js'));
assert(glob.match(p, '\\foo.js'));
assert(glob.match(p, 'testing/foo.js'));
assert(glob.match(p, 'testing\\foo.js'));
assert(glob.match(p, '/testing/foo.js'));
assert(glob.match(p, '\\testing\\foo.js'));
assert(glob.match(p, 'C:\\testing\\foo.js'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, '/foo.js');
assertGlobMatch(p, '\\foo.js');
assertGlobMatch(p, 'testing/foo.js');
assertGlobMatch(p, 'testing\\foo.js');
assertGlobMatch(p, '/testing/foo.js');
assertGlobMatch(p, '\\testing\\foo.js');
assertGlobMatch(p, 'C:\\testing\\foo.js');
assert(!glob.match(p, 'foo.ts'));
assert(!glob.match(p, 'testing/foo.ts'));
assert(!glob.match(p, 'testing\\foo.ts'));
assert(!glob.match(p, '/testing/foo.ts'));
assert(!glob.match(p, '\\testing\\foo.ts'));
assert(!glob.match(p, 'C:\\testing\\foo.ts'));
assertNoGlobMatch(p, 'foo.ts');
assertNoGlobMatch(p, 'testing/foo.ts');
assertNoGlobMatch(p, 'testing\\foo.ts');
assertNoGlobMatch(p, '/testing/foo.ts');
assertNoGlobMatch(p, '\\testing\\foo.ts');
assertNoGlobMatch(p, 'C:\\testing\\foo.ts');
assert(!glob.match(p, 'foo.js.txt'));
assert(!glob.match(p, 'testing/foo.js.txt'));
assert(!glob.match(p, 'testing\\foo.js.txt'));
assert(!glob.match(p, '/testing/foo.js.txt'));
assert(!glob.match(p, '\\testing\\foo.js.txt'));
assert(!glob.match(p, 'C:\\testing\\foo.js.txt'));
assertNoGlobMatch(p, 'foo.js.txt');
assertNoGlobMatch(p, 'testing/foo.js.txt');
assertNoGlobMatch(p, 'testing\\foo.js.txt');
assertNoGlobMatch(p, '/testing/foo.js.txt');
assertNoGlobMatch(p, '\\testing\\foo.js.txt');
assertNoGlobMatch(p, 'C:\\testing\\foo.js.txt');
assert(!glob.match(p, 'testing.js/foo'));
assert(!glob.match(p, 'testing.js\\foo'));
assert(!glob.match(p, '/testing.js/foo'));
assert(!glob.match(p, '\\testing.js\\foo'));
assert(!glob.match(p, 'C:\\testing.js\\foo'));
assertNoGlobMatch(p, 'testing.js/foo');
assertNoGlobMatch(p, 'testing.js\\foo');
assertNoGlobMatch(p, '/testing.js/foo');
assertNoGlobMatch(p, '\\testing.js\\foo');
assertNoGlobMatch(p, 'C:\\testing.js\\foo');
p = '**/foo.js';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, '/foo.js'));
assert(glob.match(p, '\\foo.js'));
assert(glob.match(p, 'testing/foo.js'));
assert(glob.match(p, 'testing\\foo.js'));
assert(glob.match(p, '/testing/foo.js'));
assert(glob.match(p, '\\testing\\foo.js'));
assert(glob.match(p, 'C:\\testing\\foo.js'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, '/foo.js');
assertGlobMatch(p, '\\foo.js');
assertGlobMatch(p, 'testing/foo.js');
assertGlobMatch(p, 'testing\\foo.js');
assertGlobMatch(p, '/testing/foo.js');
assertGlobMatch(p, '\\testing\\foo.js');
assertGlobMatch(p, 'C:\\testing\\foo.js');
});
test('cached properly', function () {
let p = '**/*.js';
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'testing/foo.js'));
assert(glob.match(p, 'testing\\foo.js'));
assert(glob.match(p, '/testing/foo.js'));
assert(glob.match(p, '\\testing\\foo.js'));
assert(glob.match(p, 'C:\\testing\\foo.js'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'testing/foo.js');
assertGlobMatch(p, 'testing\\foo.js');
assertGlobMatch(p, '/testing/foo.js');
assertGlobMatch(p, '\\testing\\foo.js');
assertGlobMatch(p, 'C:\\testing\\foo.js');
assert(!glob.match(p, 'foo.ts'));
assert(!glob.match(p, 'testing/foo.ts'));
assert(!glob.match(p, 'testing\\foo.ts'));
assert(!glob.match(p, '/testing/foo.ts'));
assert(!glob.match(p, '\\testing\\foo.ts'));
assert(!glob.match(p, 'C:\\testing\\foo.ts'));
assertNoGlobMatch(p, 'foo.ts');
assertNoGlobMatch(p, 'testing/foo.ts');
assertNoGlobMatch(p, 'testing\\foo.ts');
assertNoGlobMatch(p, '/testing/foo.ts');
assertNoGlobMatch(p, '\\testing\\foo.ts');
assertNoGlobMatch(p, 'C:\\testing\\foo.ts');
assert(!glob.match(p, 'foo.js.txt'));
assert(!glob.match(p, 'testing/foo.js.txt'));
assert(!glob.match(p, 'testing\\foo.js.txt'));
assert(!glob.match(p, '/testing/foo.js.txt'));
assert(!glob.match(p, '\\testing\\foo.js.txt'));
assert(!glob.match(p, 'C:\\testing\\foo.js.txt'));
assertNoGlobMatch(p, 'foo.js.txt');
assertNoGlobMatch(p, 'testing/foo.js.txt');
assertNoGlobMatch(p, 'testing\\foo.js.txt');
assertNoGlobMatch(p, '/testing/foo.js.txt');
assertNoGlobMatch(p, '\\testing\\foo.js.txt');
assertNoGlobMatch(p, 'C:\\testing\\foo.js.txt');
assert(!glob.match(p, 'testing.js/foo'));
assert(!glob.match(p, 'testing.js\\foo'));
assert(!glob.match(p, '/testing.js/foo'));
assert(!glob.match(p, '\\testing.js\\foo'));
assert(!glob.match(p, 'C:\\testing.js\\foo'));
assertNoGlobMatch(p, 'testing.js/foo');
assertNoGlobMatch(p, 'testing.js\\foo');
assertNoGlobMatch(p, '/testing.js/foo');
assertNoGlobMatch(p, '\\testing.js\\foo');
assertNoGlobMatch(p, 'C:\\testing.js\\foo');
// Run again and make sure the regex are properly reused
assert(glob.match(p, 'foo.js'));
assert(glob.match(p, 'testing/foo.js'));
assert(glob.match(p, 'testing\\foo.js'));
assert(glob.match(p, '/testing/foo.js'));
assert(glob.match(p, '\\testing\\foo.js'));
assert(glob.match(p, 'C:\\testing\\foo.js'));
assertGlobMatch(p, 'foo.js');
assertGlobMatch(p, 'testing/foo.js');
assertGlobMatch(p, 'testing\\foo.js');
assertGlobMatch(p, '/testing/foo.js');
assertGlobMatch(p, '\\testing\\foo.js');
assertGlobMatch(p, 'C:\\testing\\foo.js');
assert(!glob.match(p, 'foo.ts'));
assert(!glob.match(p, 'testing/foo.ts'));
assert(!glob.match(p, 'testing\\foo.ts'));
assert(!glob.match(p, '/testing/foo.ts'));
assert(!glob.match(p, '\\testing\\foo.ts'));
assert(!glob.match(p, 'C:\\testing\\foo.ts'));
assertNoGlobMatch(p, 'foo.ts');
assertNoGlobMatch(p, 'testing/foo.ts');
assertNoGlobMatch(p, 'testing\\foo.ts');
assertNoGlobMatch(p, '/testing/foo.ts');
assertNoGlobMatch(p, '\\testing\\foo.ts');
assertNoGlobMatch(p, 'C:\\testing\\foo.ts');
assert(!glob.match(p, 'foo.js.txt'));
assert(!glob.match(p, 'testing/foo.js.txt'));
assert(!glob.match(p, 'testing\\foo.js.txt'));
assert(!glob.match(p, '/testing/foo.js.txt'));
assert(!glob.match(p, '\\testing\\foo.js.txt'));
assert(!glob.match(p, 'C:\\testing\\foo.js.txt'));
assertNoGlobMatch(p, 'foo.js.txt');
assertNoGlobMatch(p, 'testing/foo.js.txt');
assertNoGlobMatch(p, 'testing\\foo.js.txt');
assertNoGlobMatch(p, '/testing/foo.js.txt');
assertNoGlobMatch(p, '\\testing\\foo.js.txt');
assertNoGlobMatch(p, 'C:\\testing\\foo.js.txt');
assert(!glob.match(p, 'testing.js/foo'));
assert(!glob.match(p, 'testing.js\\foo'));
assert(!glob.match(p, '/testing.js/foo'));
assert(!glob.match(p, '\\testing.js\\foo'));
assert(!glob.match(p, 'C:\\testing.js\\foo'));
assertNoGlobMatch(p, 'testing.js/foo');
assertNoGlobMatch(p, 'testing.js\\foo');
assertNoGlobMatch(p, '/testing.js/foo');
assertNoGlobMatch(p, '\\testing.js\\foo');
assertNoGlobMatch(p, 'C:\\testing.js\\foo');
});
test('invalid glob', function () {
let p = '**/*(.js';
assert(!glob.match(p, 'foo.js'));
assertNoGlobMatch(p, 'foo.js');
});
test('split glob aware', function () {
@@ -926,48 +945,48 @@ suite('Glob', () => {
test('relative pattern - glob star', function () {
if (isWindows) {
let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '**/*.cs', pathToRelative: (from, to) => path.relative(from, to) };
assert(glob.match(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'));
assert(glob.match(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs'));
assert(!glob.match(p, 'C:\\DNXConsoleApp\\foo\\Program.ts'));
assert(!glob.match(p, 'C:\\DNXConsoleApp\\Program.cs'));
assert(!glob.match(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts'));
assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs');
assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs');
assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.ts');
assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs');
assertNoGlobMatch(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts');
} else {
let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '**/*.cs', pathToRelative: (from, to) => path.relative(from, to) };
assert(glob.match(p, '/DNXConsoleApp/foo/Program.cs'));
assert(glob.match(p, '/DNXConsoleApp/foo/bar/Program.cs'));
assert(!glob.match(p, '/DNXConsoleApp/foo/Program.ts'));
assert(!glob.match(p, '/DNXConsoleApp/Program.cs'));
assert(!glob.match(p, '/other/DNXConsoleApp/foo/Program.ts'));
assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs');
assertGlobMatch(p, '/DNXConsoleApp/foo/bar/Program.cs');
assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.ts');
assertNoGlobMatch(p, '/DNXConsoleApp/Program.cs');
assertNoGlobMatch(p, '/other/DNXConsoleApp/foo/Program.ts');
}
});
test('relative pattern - single star', function () {
if (isWindows) {
let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '*.cs', pathToRelative: (from, to) => path.relative(from, to) };
assert(glob.match(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'));
assert(!glob.match(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs'));
assert(!glob.match(p, 'C:\\DNXConsoleApp\\foo\\Program.ts'));
assert(!glob.match(p, 'C:\\DNXConsoleApp\\Program.cs'));
assert(!glob.match(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts'));
assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs');
assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs');
assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.ts');
assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs');
assertNoGlobMatch(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts');
} else {
let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '*.cs', pathToRelative: (from, to) => path.relative(from, to) };
assert(glob.match(p, '/DNXConsoleApp/foo/Program.cs'));
assert(!glob.match(p, '/DNXConsoleApp/foo/bar/Program.cs'));
assert(!glob.match(p, '/DNXConsoleApp/foo/Program.ts'));
assert(!glob.match(p, '/DNXConsoleApp/Program.cs'));
assert(!glob.match(p, '/other/DNXConsoleApp/foo/Program.ts'));
assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs');
assertNoGlobMatch(p, '/DNXConsoleApp/foo/bar/Program.cs');
assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.ts');
assertNoGlobMatch(p, '/DNXConsoleApp/Program.cs');
assertNoGlobMatch(p, '/other/DNXConsoleApp/foo/Program.ts');
}
});
test('relative pattern - single star with path', function () {
if (isWindows) {
let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'something/*.cs', pathToRelative: (from, to) => path.relative(from, to) };
assert(glob.match(p, 'C:\\DNXConsoleApp\\foo\\something\\Program.cs'));
assert(!glob.match(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'));
assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\something\\Program.cs');
assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs');
} else {
let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'something/*.cs', pathToRelative: (from, to) => path.relative(from, to) };
assert(glob.match(p, '/DNXConsoleApp/foo/something/Program.cs'));
assert(!glob.match(p, '/DNXConsoleApp/foo/Program.cs'));
assertGlobMatch(p, '/DNXConsoleApp/foo/something/Program.cs');
assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs');
}
});

View File

@@ -234,7 +234,7 @@ export class FileService implements IFileService {
const legacyWindowsWatcher = new WindowsWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
this.activeWorkspaceFileChangeWatcher = toDisposable(legacyWindowsWatcher.startWatching());
} else {
const legacyUnixWatcher = new UnixWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
const legacyUnixWatcher = new UnixWatcherService(this.contextService, this.configurationService, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
this.activeWorkspaceFileChangeWatcher = toDisposable(legacyUnixWatcher.startWatching());
}
}

View File

@@ -10,6 +10,8 @@ import * as fs from 'fs';
import * as gracefulFs from 'graceful-fs';
gracefulFs.gracefulify(fs);
import * as paths from 'vs/base/common/paths';
import * as glob from 'vs/base/common/glob';
import { TPromise } from 'vs/base/common/winjs.base';
import { FileChangeType } from 'vs/platform/files/common/files';
@@ -18,158 +20,331 @@ import * as strings from 'vs/base/common/strings';
import { normalizeNFC } from 'vs/base/common/normalization';
import { realcaseSync } from 'vs/base/node/extfs';
import { isMacintosh } from 'vs/base/common/platform';
import * as watcher from 'vs/workbench/services/files/node/watcher/common';
import { IWatcherRequest, IWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcher';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import * as watcherCommon from 'vs/workbench/services/files/node/watcher/common';
import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher';
interface IWatcher {
requests: ExtendedWatcherRequest[];
stop(): any;
}
export interface IChockidarWatcherOptions {
pollingInterval?: number;
}
interface ExtendedWatcherRequest extends IWatcherRequest {
parsedPattern?: glob.ParsedPattern;
}
export class ChokidarWatcherService implements IWatcherService {
private static readonly FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms)
private static readonly EVENT_SPAM_WARNING_THRESHOLD = 60 * 1000; // warn after certain time span of event spam
private _watchers: { [watchPath: string]: IWatcher };
private _watcherCount: number;
private _watcherPromise: TPromise<void>;
private _options: IWatcherOptions & IChockidarWatcherOptions;
private spamCheckStartTime: number;
private spamWarningLogged: boolean;
private enospcErrorLogged: boolean;
private toDispose: IDisposable[] = [];
private _errorCallback: (error: Error) => void;
private _fileChangeCallback: (changes: watcherCommon.IRawFileChange[]) => void;
public initialize(options: IWatcherOptions & IChockidarWatcherOptions): TPromise<void> {
this._options = options;
this._watchers = Object.create(null);
this._watcherCount = 0;
this._watcherPromise = new TPromise<void>((c, e, p) => {
this._errorCallback = (error) => {
this.stop();
e(error);
};
this._fileChangeCallback = p;
}, () => {
this.stop();
});
return this._watcherPromise;
}
public setRoots(requests: IWatcherRequest[]): TPromise<void> {
const watchers = Object.create(null);
const newRequests = [];
const requestsByBasePath = normalizeRoots(requests);
// evaluate new & remaining watchers
for (let basePath in requestsByBasePath) {
let watcher = this._watchers[basePath];
if (watcher && isEqualRequests(watcher.requests, requestsByBasePath[basePath])) {
watchers[basePath] = watcher;
delete this._watchers[basePath];
} else {
newRequests.push(basePath);
}
}
// stop all old watchers
for (let path in this._watchers) {
this._watchers[path].stop();
}
// start all new watchers
for (let basePath of newRequests) {
let requests = requestsByBasePath[basePath];
watchers[basePath] = this._watch(basePath, requests);
}
this._watchers = watchers;
return TPromise.as(null);
}
// for test purposes
public get wacherCount() {
return this._watcherCount;
}
private _watch(basePath: string, requests: IWatcherRequest[]): IWatcher {
if (this._options.verboseLogging) {
console.log(`Start watching: ${basePath}]`);
}
const pollingInterval = this._options.pollingInterval || 1000;
public watch(request: IWatcherRequest): TPromise<void> {
const watcherOpts: chokidar.IOptions = {
ignoreInitial: true,
ignorePermissionErrors: true,
followSymlinks: true, // this is the default of chokidar and supports file events through symlinks
ignored: request.ignored,
interval: 1000, // while not used in normal cases, if any error causes chokidar to fallback to polling, increase its intervals
binaryInterval: 1000,
interval: pollingInterval, // while not used in normal cases, if any error causes chokidar to fallback to polling, increase its intervals
binaryInterval: pollingInterval,
disableGlobbing: true // fix https://github.com/Microsoft/vscode/issues/4586
};
// if there's only one request, use the built-in ignore-filterering
if (requests.length === 1) {
watcherOpts.ignored = requests[0].ignored;
}
// Chokidar fails when the basePath does not match case-identical to the path on disk
// so we have to find the real casing of the path and do some path massaging to fix this
// see https://github.com/paulmillr/chokidar/issues/418
const originalBasePath = request.basePath;
const realBasePath = isMacintosh ? (realcaseSync(originalBasePath) || originalBasePath) : originalBasePath;
const realBasePath = isMacintosh ? (realcaseSync(basePath) || basePath) : basePath;
const realBasePathLength = realBasePath.length;
const realBasePathDiffers = (originalBasePath !== realBasePath);
const realBasePathDiffers = (basePath !== realBasePath);
if (realBasePathDiffers) {
console.warn(`Watcher basePath does not match version on disk and was corrected (original: ${originalBasePath}, real: ${realBasePath})`);
console.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`);
}
const chokidarWatcher = chokidar.watch(realBasePath, watcherOpts);
let chokidarWatcher = chokidar.watch(realBasePath, watcherOpts);
this._watcherCount++;
// Detect if for some reason the native watcher library fails to load
if (isMacintosh && !chokidarWatcher.options.useFsEvents) {
console.error('Watcher is not using native fsevents library and is falling back to unefficient polling.');
}
let undeliveredFileEvents: watcher.IRawFileChange[] = [];
const fileEventDelayer = new ThrottledDelayer(ChokidarWatcherService.FS_EVENT_DELAY);
let undeliveredFileEvents: watcherCommon.IRawFileChange[] = [];
let fileEventDelayer = new ThrottledDelayer(ChokidarWatcherService.FS_EVENT_DELAY);
this.toDispose.push(toDisposable(() => {
chokidarWatcher.close();
fileEventDelayer.cancel();
}));
return new TPromise<void>((c, e, p) => {
chokidarWatcher.on('all', (type: string, path: string) => {
if (isMacintosh) {
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
path = normalizeNFC(path);
}
if (path.indexOf(realBasePath) < 0) {
return; // we really only care about absolute paths here in our basepath context here
}
// Make sure to convert the path back to its original basePath form if the realpath is different
if (realBasePathDiffers) {
path = originalBasePath + path.substr(realBasePathLength);
}
let event: watcher.IRawFileChange = null;
// Change
if (type === 'change') {
event = { type: 0, path };
}
// Add
else if (type === 'add' || type === 'addDir') {
event = { type: 1, path };
}
// Delete
else if (type === 'unlink' || type === 'unlinkDir') {
event = { type: 2, path };
}
if (event) {
// Logging
if (request.verboseLogging) {
console.log(event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]', event.path);
const watcher: IWatcher = {
requests,
stop: () => {
try {
if (this._options.verboseLogging) {
console.log(`Stop watching: ${basePath}]`);
}
// Check for spam
const now = Date.now();
if (undeliveredFileEvents.length === 0) {
this.spamWarningLogged = false;
this.spamCheckStartTime = now;
} else if (!this.spamWarningLogged && this.spamCheckStartTime + ChokidarWatcherService.EVENT_SPAM_WARNING_THRESHOLD < now) {
this.spamWarningLogged = true;
console.warn(strings.format('Watcher is busy catching up with {0} file changes in 60 seconds. Latest changed path is "{1}"', undeliveredFileEvents.length, event.path));
if (chokidarWatcher) {
chokidarWatcher.close();
this._watcherCount--;
chokidarWatcher = null;
}
if (fileEventDelayer) {
fileEventDelayer.cancel();
fileEventDelayer = null;
}
} catch (error) {
console.error(error.toString());
}
}
};
// Add to buffer
undeliveredFileEvents.push(event);
chokidarWatcher.on('all', (type: string, path: string) => {
if (isMacintosh) {
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
path = normalizeNFC(path);
}
// Delay and send buffer
fileEventDelayer.trigger(() => {
const events = undeliveredFileEvents;
undeliveredFileEvents = [];
if (path.indexOf(realBasePath) < 0) {
return; // we really only care about absolute paths here in our basepath context here
}
// Broadcast to clients normalized
const res = watcher.normalize(events);
p(res);
// Make sure to convert the path back to its original basePath form if the realpath is different
if (realBasePathDiffers) {
path = basePath + path.substr(realBasePathLength);
}
// Logging
if (request.verboseLogging) {
res.forEach(r => {
console.log(' >> normalized', r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]', r.path);
});
}
let eventType: FileChangeType;
switch (type) {
case 'change':
eventType = FileChangeType.UPDATED;
break;
case 'add':
case 'addDir':
eventType = FileChangeType.ADDED;
break;
case 'unlink':
case 'unlinkDir':
eventType = FileChangeType.DELETED;
break;
default:
return;
}
return TPromise.as(null);
if (isIgnored(path, watcher.requests)) {
return;
}
let event = { type: eventType, path };
// Logging
if (this._options.verboseLogging) {
console.log(eventType === FileChangeType.ADDED ? '[ADDED]' : eventType === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]', path);
}
// Check for spam
const now = Date.now();
if (undeliveredFileEvents.length === 0) {
this.spamWarningLogged = false;
this.spamCheckStartTime = now;
} else if (!this.spamWarningLogged && this.spamCheckStartTime + ChokidarWatcherService.EVENT_SPAM_WARNING_THRESHOLD < now) {
this.spamWarningLogged = true;
console.warn(strings.format('Watcher is busy catching up with {0} file changes in 60 seconds. Latest changed path is "{1}"', undeliveredFileEvents.length, event.path));
}
// Add to buffer
undeliveredFileEvents.push(event);
// Delay and send buffer
fileEventDelayer.trigger(() => {
const events = undeliveredFileEvents;
undeliveredFileEvents = [];
// Broadcast to clients normalized
const res = watcherCommon.normalize(events);
this._fileChangeCallback(res);
// Logging
if (this._options.verboseLogging) {
res.forEach(r => {
console.log(' >> normalized', r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]', r.path);
});
}
});
chokidarWatcher.on('error', (error: Error) => {
if (error) {
// Specially handle ENOSPC errors that can happen when
// the watcher consumes so many file descriptors that
// we are running into a limit. We only want to warn
// once in this case to avoid log spam.
// See https://github.com/Microsoft/vscode/issues/7950
if ((<any>error).code === 'ENOSPC') {
if (!this.enospcErrorLogged) {
this.enospcErrorLogged = true;
e(new Error('Inotify limit reached (ENOSPC)'));
}
} else {
console.error(error.toString());
}
}
return TPromise.as(null);
});
}, () => {
this.toDispose = dispose(this.toDispose);
});
chokidarWatcher.on('error', (error: Error) => {
if (error) {
// Specially handle ENOSPC errors that can happen when
// the watcher consumes so many file descriptors that
// we are running into a limit. We only want to warn
// once in this case to avoid log spam.
// See https://github.com/Microsoft/vscode/issues/7950
if ((<any>error).code === 'ENOSPC') {
if (!this.enospcErrorLogged) {
this.enospcErrorLogged = true;
this._errorCallback(new Error('Inotify limit reached (ENOSPC)'));
}
} else {
console.error(error.toString());
}
}
});
return watcher;
}
public stop(): TPromise<void> {
this.toDispose = dispose(this.toDispose);
for (let path in this._watchers) {
let watcher = this._watchers[path];
watcher.stop();
}
this._watchers = Object.create(null);
return TPromise.as(void 0);
}
}
function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean {
for (let request of requests) {
if (request.basePath === path) {
return false;
}
if (paths.isEqualOrParent(path, request.basePath)) {
if (!request.parsedPattern) {
if (request.ignored && request.ignored.length > 0) {
let pattern = `{${request.ignored.map(i => i + '/**').join(',')}}`;
request.parsedPattern = glob.parse(pattern);
} else {
request.parsedPattern = () => false;
}
}
const relPath = path.substr(request.basePath.length + 1);
if (!request.parsedPattern(relPath)) {
return false;
}
}
}
return true;
}
/**
* Normalizes a set of root paths by grouping by the most parent root path.
* equests with Sub paths are skipped if they have the same ignored set as the parent.
*/
export function normalizeRoots(requests: IWatcherRequest[]): { [basePath: string]: IWatcherRequest[] } {
requests = requests.sort((r1, r2) => r1.basePath.localeCompare(r2.basePath));
let prevRequest: IWatcherRequest = null;
let result: { [basePath: string]: IWatcherRequest[] } = Object.create(null);
for (let request of requests) {
let basePath = request.basePath;
let ignored = (request.ignored || []).sort();
if (prevRequest && (paths.isEqualOrParent(basePath, prevRequest.basePath))) {
if (!isEqualIgnore(ignored, prevRequest.ignored)) {
result[prevRequest.basePath].push({ basePath, ignored });
}
} else {
prevRequest = { basePath, ignored };
result[basePath] = [prevRequest];
}
}
return result;
}
function isEqualRequests(r1: IWatcherRequest[], r2: IWatcherRequest[]) {
if (r1.length !== r2.length) {
return false;
}
for (let k = 0; k < r1.length; k++) {
if (r1[k].basePath !== r2[k].basePath || !isEqualIgnore(r1[k].ignored, r2[k].ignored)) {
return false;
}
}
return true;
}
function isEqualIgnore(i1: string[], i2: string[]) {
if (i1.length !== i2.length) {
return false;
}
for (let k = 0; k < i1.length; k++) {
if (i1[k] !== i2[k]) {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,327 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
import { normalizeRoots, ChokidarWatcherService } from '../chokidarWatcherService';
import { IWatcherRequest } from '../watcher';
import * as platform from 'vs/base/common/platform';
import { Delayer } from 'vs/base/common/async';
import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
import { FileChangeType } from 'vs/platform/files/common/files';
function newRequest(basePath: string, ignored = []): IWatcherRequest {
return { basePath, ignored };
}
function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) {
const requests = inputPaths.map(path => newRequest(path));
const actual = normalizeRoots(requests);
assert.deepEqual(Object.keys(actual).sort(), expectedPaths);
}
function assertNormalizedRequests(inputRequests: IWatcherRequest[], expectedRequests: { [path: string]: IWatcherRequest[] }) {
const actual = normalizeRoots(inputRequests);
const actualPath = Object.keys(actual).sort();
const expectedPaths = Object.keys(expectedRequests).sort();
assert.deepEqual(actualPath, expectedPaths);
for (let path of actualPath) {
let a = expectedRequests[path].sort((r1, r2) => r1.basePath.localeCompare(r2.basePath));
let e = expectedRequests[path].sort((r1, r2) => r1.basePath.localeCompare(r2.basePath));
assert.deepEqual(a, e);
}
}
function sort(changes: IRawFileChange[]) {
return changes.sort((c1, c2) => {
return c1.path.localeCompare(c2.path);
});
}
function wait(time: number) {
return new Delayer<void>(time).trigger(() => { });
}
async function assertFileEvents(actuals: IRawFileChange[], expected: IRawFileChange[]) {
let repeats = 40;
while ((actuals.length < expected.length) && repeats-- > 0) {
await wait(50);
}
assert.deepEqual(sort(actuals), sort(expected));
actuals.length = 0;
}
suite('Chockidar normalizeRoots', () => {
test('should not impacts roots that don\'t overlap', () => {
if (platform.isWindows) {
assertNormalizedRootPath(['C:\\a'], ['C:\\a']);
assertNormalizedRootPath(['C:\\a', 'C:\\b'], ['C:\\a', 'C:\\b']);
assertNormalizedRootPath(['C:\\a', 'C:\\b', 'C:\\c\\d\\e'], ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']);
} else {
assertNormalizedRootPath(['/a'], ['/a']);
assertNormalizedRootPath(['/a', '/b'], ['/a', '/b']);
assertNormalizedRootPath(['/a', '/b', '/c/d/e'], ['/a', '/b', '/c/d/e']);
}
});
test('should remove sub-folders of other roots', () => {
if (platform.isWindows) {
assertNormalizedRootPath(['C:\\a', 'C:\\a\\b'], ['C:\\a']);
assertNormalizedRootPath(['C:\\a', 'C:\\b', 'C:\\a\\b'], ['C:\\a', 'C:\\b']);
assertNormalizedRootPath(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b'], ['C:\\a', 'C:\\b']);
assertNormalizedRootPath(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d'], ['C:\\a']);
} else {
assertNormalizedRootPath(['/a', '/a/b'], ['/a']);
assertNormalizedRootPath(['/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/b/a', '/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/a', '/a/b', '/a/c/d'], ['/a']);
assertNormalizedRootPath(['/a/c/d/e', '/a/b/d', '/a/c/d', '/a/c/e/f', '/a/b'], ['/a/b', '/a/c/d', '/a/c/e/f']);
}
});
test('should remove duplicates', () => {
if (platform.isWindows) {
assertNormalizedRootPath(['C:\\a', 'C:\\a\\', 'C:\\a'], ['C:\\a']);
} else {
assertNormalizedRootPath(['/a', '/a/', '/a'], ['/a']);
assertNormalizedRootPath(['/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/b/a', '/a', '/b', '/a/b'], ['/a', '/b']);
assertNormalizedRootPath(['/a', '/a/b', '/a/c/d'], ['/a']);
}
});
test('nested requests', () => {
let p1, p2, p3;
if (platform.isWindows) {
p1 = 'C:\\a';
p2 = 'C:\\a\\b';
p3 = 'C:\\a\\b\\c';
} else {
p1 = '/a';
p2 = '/a/b';
p3 = '/a/b/c';
}
const r1 = newRequest(p1, ['**/*.ts']);
const r2 = newRequest(p2, ['**/*.js']);
const r3 = newRequest(p3, ['**/*.ts']);
assertNormalizedRequests([r1, r2], { [p1]: [r1, r2] });
assertNormalizedRequests([r2, r1], { [p1]: [r1, r2] });
assertNormalizedRequests([r1, r2, r3], { [p1]: [r1, r2, r3] });
assertNormalizedRequests([r1, r3], { [p1]: [r1] });
assertNormalizedRequests([r2, r3], { [p2]: [r2, r3] });
});
});
suite('Chockidar watching', () => {
const tmpdir = os.tmpdir();
const testDir = path.join(tmpdir, 'chockidartest-' + Date.now());
const aFolder = path.join(testDir, 'a');
const bFolder = path.join(testDir, 'b');
const b2Folder = path.join(bFolder, 'b2');
const service = new ChokidarWatcherService();
const result: IRawFileChange[] = [];
let error = null;
suiteSetup(async () => {
await pfs.mkdirp(testDir);
await pfs.mkdirp(aFolder);
await pfs.mkdirp(bFolder);
await pfs.mkdirp(b2Folder);
const promise = service.initialize({ verboseLogging: false, pollingInterval: 200 });
promise.then(null,
e => {
console.log('set error', e);
error = e;
},
p => {
if (Array.isArray(p)) {
result.push(...p);
}
}
);
});
suiteTeardown(async () => {
await pfs.del(testDir);
await service.stop();
});
setup(() => {
result.length = 0;
assert.equal(error, null);
});
teardown(() => {
assert.equal(error, null);
});
test('simple file operations, single root, no ignore', async () => {
let request: IWatcherRequest = { basePath: testDir, ignored: [] };
service.setRoots([request]);
await wait(300);
assert.equal(service.wacherCount, 1);
// create a file
let testFilePath = path.join(testDir, 'file.txt');
await pfs.writeFile(testFilePath, '');
await assertFileEvents(result, [{ path: testFilePath, type: FileChangeType.ADDED }]);
// modify a file
await pfs.writeFile(testFilePath, 'Hello');
await assertFileEvents(result, [{ path: testFilePath, type: FileChangeType.UPDATED }]);
// create a folder
let testFolderPath = path.join(testDir, 'newFolder');
await pfs.mkdirp(testFolderPath);
// copy a file
let copiedFilePath = path.join(testFolderPath, 'file2.txt');
await pfs.copy(testFilePath, copiedFilePath);
await assertFileEvents(result, [{ path: copiedFilePath, type: FileChangeType.ADDED }, { path: testFolderPath, type: FileChangeType.ADDED }]);
// delete a file
await pfs.del(copiedFilePath);
let renamedFilePath = path.join(testFolderPath, 'file3.txt');
// move a file
await pfs.rename(testFilePath, renamedFilePath);
await assertFileEvents(result, [{ path: copiedFilePath, type: FileChangeType.DELETED }, { path: testFilePath, type: FileChangeType.DELETED }, { path: renamedFilePath, type: FileChangeType.ADDED }]);
// delete a folder
await pfs.del(testFolderPath);
await assertFileEvents(result, [{ path: testFolderPath, type: FileChangeType.DELETED }, { path: renamedFilePath, type: FileChangeType.DELETED }]);
});
test('simple file operations, ignore', async () => {
let request: IWatcherRequest = { basePath: testDir, ignored: ['**/b', '**/*.js', '.git'] };
service.setRoots([request]);
await wait(300);
assert.equal(service.wacherCount, 1);
// create various ignored files
let file1 = path.join(bFolder, 'file1.txt'); // hidden
await pfs.writeFile(file1, 'Hello');
let file2 = path.join(b2Folder, 'file2.txt'); // hidden
await pfs.writeFile(file2, 'Hello');
let folder1 = path.join(bFolder, 'folder1'); // hidden
await pfs.mkdirp(folder1);
let folder2 = path.join(aFolder, 'b'); // hidden
await pfs.mkdirp(folder2);
let folder3 = path.join(testDir, '.git'); // hidden
await pfs.mkdirp(folder3);
let folder4 = path.join(testDir, '.git1');
await pfs.mkdirp(folder4);
let folder5 = path.join(aFolder, '.git');
await pfs.mkdirp(folder5);
let file3 = path.join(aFolder, 'file3.js'); // hidden
await pfs.writeFile(file3, 'var x;');
let file4 = path.join(aFolder, 'file4.txt');
await pfs.writeFile(file4, 'Hello');
await assertFileEvents(result, [{ path: file4, type: FileChangeType.ADDED }, { path: folder4, type: FileChangeType.ADDED }, { path: folder5, type: FileChangeType.ADDED }]);
// move some files
let movedFile1 = path.join(folder2, 'file1.txt'); // from ignored to ignored
await pfs.rename(file1, movedFile1);
let movedFile2 = path.join(aFolder, 'file2.txt'); // from ignored to visible
await pfs.rename(file2, movedFile2);
let movedFile3 = path.join(aFolder, 'file3.txt'); // from ignored file ext to visible
await pfs.rename(file3, movedFile3);
await assertFileEvents(result, [{ path: movedFile2, type: FileChangeType.ADDED }, { path: movedFile3, type: FileChangeType.ADDED }]);
// delete all files
await pfs.del(movedFile1); // hidden
await pfs.del(movedFile2);
await pfs.del(movedFile3);
await pfs.del(folder1); // hidden
await pfs.del(folder2); // hidden
await pfs.del(folder3); // hidden
await pfs.del(folder4);
await pfs.del(folder5);
await pfs.del(file4);
await assertFileEvents(result, [{ path: movedFile2, type: FileChangeType.DELETED }, { path: movedFile3, type: FileChangeType.DELETED }, { path: file4, type: FileChangeType.DELETED }, { path: folder4, type: FileChangeType.DELETED }, { path: folder5, type: FileChangeType.DELETED }]);
});
test('simple file operations, multiple roots', async () => {
let request1: IWatcherRequest = { basePath: aFolder, ignored: ['**/*.js'] };
let request2: IWatcherRequest = { basePath: b2Folder, ignored: ['**/*.ts'] };
service.setRoots([request1, request2]);
await wait(300);
assert.equal(service.wacherCount, 2);
// create some files
let folderPath1 = path.join(aFolder, 'folder1');
await pfs.mkdirp(folderPath1);
let filePath1 = path.join(folderPath1, 'file1.json');
await pfs.writeFile(filePath1, '');
let filePath2 = path.join(folderPath1, 'file2.js'); // filtered
await pfs.writeFile(filePath2, '');
let folderPath2 = path.join(b2Folder, 'folder2');
await pfs.mkdirp(folderPath2);
let filePath3 = path.join(folderPath2, 'file3.ts'); // filtered
await pfs.writeFile(filePath3, '');
let filePath4 = path.join(testDir, 'file4.json'); // outside roots
await pfs.writeFile(filePath4, '');
await assertFileEvents(result, [{ path: folderPath1, type: FileChangeType.ADDED }, { path: filePath1, type: FileChangeType.ADDED }, { path: folderPath2, type: FileChangeType.ADDED }]);
// change roots
let request3: IWatcherRequest = { basePath: aFolder, ignored: ['**/*.json'] };
service.setRoots([request3]);
await wait(300);
assert.equal(service.wacherCount, 1);
// delete all
await pfs.del(folderPath1);
await pfs.del(folderPath2);
await pfs.del(filePath4);
await assertFileEvents(result, [{ path: folderPath1, type: FileChangeType.DELETED }, { path: filePath2, type: FileChangeType.DELETED }]);
});
test('simple file operations, nested roots', async () => {
let request1: IWatcherRequest = { basePath: testDir, ignored: ['**/b2'] };
let request2: IWatcherRequest = { basePath: bFolder, ignored: ['**/b3'] };
service.setRoots([request1, request2]);
await wait(300);
assert.equal(service.wacherCount, 1);
// create files
let filePath1 = path.join(bFolder, 'file1.xml'); // visible by both
await pfs.writeFile(filePath1, '');
let filePath2 = path.join(b2Folder, 'file2.xml'); // filtered by root1, but visible by root2
await pfs.writeFile(filePath2, '');
let folderPath1 = path.join(b2Folder, 'b3'); // filtered
await pfs.mkdirp(folderPath1);
let filePath3 = path.join(folderPath1, 'file3.xml'); // filtered
await pfs.writeFile(filePath3, '');
await assertFileEvents(result, [{ path: filePath1, type: FileChangeType.ADDED }, { path: filePath2, type: FileChangeType.ADDED }]);
let renamedFilePath2 = path.join(folderPath1, 'file2.xml');
// move a file
await pfs.rename(filePath2, renamedFilePath2);
await assertFileEvents(result, [{ path: filePath2, type: FileChangeType.DELETED }]);
// delete all
await pfs.del(folderPath1);
await pfs.del(filePath1);
await assertFileEvents(result, [{ path: filePath1, type: FileChangeType.DELETED }]);
});
});

View File

@@ -10,9 +10,13 @@ import { TPromise } from 'vs/base/common/winjs.base';
export interface IWatcherRequest {
basePath: string;
ignored: string[];
}
export interface IWatcherOptions {
verboseLogging: boolean;
}
export interface IWatcherService {
watch(request: IWatcherRequest): TPromise<void>;
initialize(options: IWatcherOptions): TPromise<void>;
setRoots(roots: IWatcherRequest[]): TPromise<void>;
}

View File

@@ -7,10 +7,11 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWatcherRequest, IWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcher';
import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher';
export interface IWatcherChannel extends IChannel {
call(command: 'watch', request: IWatcherRequest): TPromise<void>;
call(command: 'initialize', options: IWatcherOptions): TPromise<void>;
call(command: 'setRoots', request: IWatcherRequest[]): TPromise<void>;
call(command: string, arg: any): TPromise<any>;
}
@@ -20,7 +21,8 @@ export class WatcherChannel implements IWatcherChannel {
call(command: string, arg: any): TPromise<any> {
switch (command) {
case 'watch': return this.service.watch(arg);
case 'initialize': return this.service.initialize(arg);
case 'setRoots': return this.service.setRoots(arg);
}
return undefined;
}
@@ -30,7 +32,11 @@ export class WatcherChannelClient implements IWatcherService {
constructor(private channel: IWatcherChannel) { }
watch(request: IWatcherRequest): TPromise<void> {
return this.channel.call('watch', request);
initialize(options: IWatcherOptions): TPromise<void> {
return this.channel.call('initialize', options);
}
setRoots(roots: IWatcherRequest[]): TPromise<void> {
return this.channel.call('setRoots', roots);
}
}

View File

@@ -11,26 +11,31 @@ import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import uri from 'vs/base/common/uri';
import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc';
import { FileChangesEvent } from 'vs/platform/files/common/files';
import { FileChangesEvent, IFilesConfiguration } from 'vs/platform/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { normalize } from 'path';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Schemas } from 'vs/base/common/network';
export class FileWatcher {
private static readonly MAX_RESTARTS = 5;
private isDisposed: boolean;
private restartCounter: number;
private service: WatcherChannelClient;
private toDispose: IDisposable[];
constructor(
private contextService: IWorkspaceContextService,
private ignored: string[],
private configurationService: IConfigurationService,
private onFileChanges: (changes: FileChangesEvent) => void,
private errorLogger: (msg: string) => void,
private verboseLogging: boolean
) {
this.isDisposed = false;
this.restartCounter = 0;
this.toDispose = [];
}
public startWatching(): () => void {
@@ -48,17 +53,19 @@ export class FileWatcher {
}
}
);
this.toDispose.push(client);
const channel = getNextTickChannel(client.getChannel<IWatcherChannel>('watcher'));
const service = new WatcherChannelClient(channel);
this.service = new WatcherChannelClient(channel);
// Start watching
const basePath: string = normalize(this.contextService.getWorkspace().folders[0].uri.fsPath);
service.watch({ basePath: basePath, ignored: this.ignored, verboseLogging: this.verboseLogging }).then(null, err => {
const options = {
verboseLogging: this.verboseLogging
};
this.service.initialize(options).then(null, err => {
if (!this.isDisposed && !isPromiseCanceledError(err)) {
return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up
}
return void 0;
}, (events: IRawFileChange[]) => this.onRawFileEvents(events)).done(() => {
@@ -79,10 +86,41 @@ export class FileWatcher {
}
});
return () => {
this.isDisposed = true;
client.dispose();
};
// Start watching
this.updateFolders();
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateFolders()));
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('files.watcherExclude')) {
this.updateFolders();
}
}));
return () => this.dispose();
}
private updateFolders() {
if (this.isDisposed) {
return;
}
this.service.setRoots(this.contextService.getWorkspace().folders.filter(folder => {
// Only workspace folders on disk
return folder.uri.scheme === Schemas.file;
}).map(folder => {
// Fetch the root's watcherExclude setting and return it
const configuration = this.configurationService.getValue<IFilesConfiguration>({
resource: folder.uri
});
let ignored: string[] = [];
if (configuration.files && configuration.files.watcherExclude) {
ignored = Object.keys(configuration.files.watcherExclude).filter(k => !!configuration.files.watcherExclude[k]);
}
return {
basePath: folder.uri.fsPath,
ignored,
recursive: false
};
}));
}
private onRawFileEvents(events: IRawFileChange[]): void {
@@ -95,4 +133,9 @@ export class FileWatcher {
this.onFileChanges(toFileChangesEvent(events));
}
}
private dispose(): void {
this.isDisposed = true;
this.toDispose = dispose(this.toDispose);
}
}