mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 18:49:00 +01:00
multi root chockidar
This commit is contained in:
2
src/typings/chokidar.d.ts
vendored
2
src/typings/chokidar.d.ts
vendored
@@ -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 {
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 }]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user