diff --git a/.github/classifier.yml b/.github/classifier.yml index ed186999d77..7fddcd73904 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -47,7 +47,7 @@ assignLabel: false }, file-explorer: { - assignees: [ isidorn ], + assignees: [ weinand ], assignLabel: false }, format: [], diff --git a/.vscode/launch.json b/.vscode/launch.json index da7e7c092a5..35ccaa15aa7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "request": "attach", "name": "Attach to Extension Host", "protocol": "inspector", - "port": 5870, + "port": 5871, // "restart": true, "outFiles": [ "${workspaceFolder}/out/**/*.js" diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 1ed150fa0ac..53b17e58366 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/bccefcd3facfca34c7821801692472ac1fce61d6", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2165b3e1139e13880d450ba1bae0801df32b1a01", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -48,6 +48,9 @@ { "include": "#decl-block" }, + { + "include": "#label" + }, { "include": "#expression" }, @@ -135,6 +138,38 @@ } ] }, + "label": { + "patterns": [ + { + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)(?=\\s*\\{)", + "beginCaptures": { + "1": { + "name": "entity.name.label.js" + }, + "2": { + "name": "punctuation.separator.label.js" + } + }, + "end": "(?<=\\{)", + "patterns": [ + { + "include": "#decl-block" + } + ] + }, + { + "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)", + "captures": { + "1": { + "name": "entity.name.label.js" + }, + "2": { + "name": "punctuation.separator.label.js" + } + } + } + ] + }, "expression": { "patterns": [ { @@ -1390,9 +1425,6 @@ } }, "patterns": [ - { - "include": "#string" - }, { "include": "#comment" }, @@ -1408,6 +1440,9 @@ { "include": "#field-declaration" }, + { + "include": "#string" + }, { "include": "#type-annotation" }, diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json index 3f59ddf8dfe..53da32f2dfe 100644 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/bccefcd3facfca34c7821801692472ac1fce61d6", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2165b3e1139e13880d450ba1bae0801df32b1a01", "name": "JavaScript (with React support)", "scopeName": "source.js.jsx", "patterns": [ @@ -48,6 +48,9 @@ { "include": "#decl-block" }, + { + "include": "#label" + }, { "include": "#expression" }, @@ -135,6 +138,38 @@ } ] }, + "label": { + "patterns": [ + { + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)(?=\\s*\\{)", + "beginCaptures": { + "1": { + "name": "entity.name.label.js.jsx" + }, + "2": { + "name": "punctuation.separator.label.js.jsx" + } + }, + "end": "(?<=\\{)", + "patterns": [ + { + "include": "#decl-block" + } + ] + }, + { + "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)", + "captures": { + "1": { + "name": "entity.name.label.js.jsx" + }, + "2": { + "name": "punctuation.separator.label.js.jsx" + } + } + } + ] + }, "expression": { "patterns": [ { @@ -1390,9 +1425,6 @@ } }, "patterns": [ - { - "include": "#string" - }, { "include": "#comment" }, @@ -1408,6 +1440,9 @@ { "include": "#field-declaration" }, + { + "include": "#string" + }, { "include": "#type-annotation" }, diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index 439ed05190e..7321af7b015 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -416,4 +416,4 @@ } } ] -} \ No newline at end of file +} diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index 42a984fab84..d4d016549ea 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/bccefcd3facfca34c7821801692472ac1fce61d6", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2165b3e1139e13880d450ba1bae0801df32b1a01", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -48,6 +48,9 @@ { "include": "#decl-block" }, + { + "include": "#label" + }, { "include": "#expression" }, @@ -135,6 +138,38 @@ } ] }, + "label": { + "patterns": [ + { + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)(?=\\s*\\{)", + "beginCaptures": { + "1": { + "name": "entity.name.label.ts" + }, + "2": { + "name": "punctuation.separator.label.ts" + } + }, + "end": "(?<=\\{)", + "patterns": [ + { + "include": "#decl-block" + } + ] + }, + { + "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)", + "captures": { + "1": { + "name": "entity.name.label.ts" + }, + "2": { + "name": "punctuation.separator.label.ts" + } + } + } + ] + }, "expression": { "patterns": [ { @@ -1387,9 +1422,6 @@ } }, "patterns": [ - { - "include": "#string" - }, { "include": "#comment" }, @@ -1405,6 +1437,9 @@ { "include": "#field-declaration" }, + { + "include": "#string" + }, { "include": "#type-annotation" }, diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index 0356ef9535e..fb007fbda4e 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/bccefcd3facfca34c7821801692472ac1fce61d6", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2165b3e1139e13880d450ba1bae0801df32b1a01", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -48,6 +48,9 @@ { "include": "#decl-block" }, + { + "include": "#label" + }, { "include": "#expression" }, @@ -135,6 +138,38 @@ } ] }, + "label": { + "patterns": [ + { + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)(?=\\s*\\{)", + "beginCaptures": { + "1": { + "name": "entity.name.label.tsx" + }, + "2": { + "name": "punctuation.separator.label.tsx" + } + }, + "end": "(?<=\\{)", + "patterns": [ + { + "include": "#decl-block" + } + ] + }, + { + "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(:)", + "captures": { + "1": { + "name": "entity.name.label.tsx" + }, + "2": { + "name": "punctuation.separator.label.tsx" + } + } + } + ] + }, "expression": { "patterns": [ { @@ -1390,9 +1425,6 @@ } }, "patterns": [ - { - "include": "#string" - }, { "include": "#comment" }, @@ -1408,6 +1440,9 @@ { "include": "#field-declaration" }, + { + "include": "#string" + }, { "include": "#type-annotation" }, diff --git a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts index 554dfc22d23..c8e2ff970fd 100644 --- a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts @@ -216,20 +216,23 @@ export default class BufferSyncSupport { this.requestDiagnostic(resource); } - private onDidCloseTextDocument(document: TextDocument): void { - const resource = document.uri; + public closeResource(resource: Uri): void { const syncedBuffer = this.syncedBuffers.get(resource); if (!syncedBuffer) { return; } - this.diagnostics.delete(resource); this.syncedBuffers.delete(resource); syncedBuffer.close(); if (!fs.existsSync(resource.fsPath)) { + this.diagnostics.delete(resource); this.requestAllDiagnostics(); } } + private onDidCloseTextDocument(document: TextDocument): void { + this.closeResource(document.uri); + } + private onDidChangeTextDocument(e: TextDocumentChangeEvent): void { const syncedBuffer = this.syncedBuffers.get(e.document.uri); if (syncedBuffer) { diff --git a/extensions/typescript-language-features/src/features/foldingProvider.ts b/extensions/typescript-language-features/src/features/foldingProvider.ts index 4a8c9ba7390..89b92c10d30 100644 --- a/extensions/typescript-language-features/src/features/foldingProvider.ts +++ b/extensions/typescript-language-features/src/features/foldingProvider.ts @@ -34,13 +34,26 @@ export default class TypeScriptFoldingProvider implements vscode.FoldingRangePro return; } - return response.body.map(span => this.convertOutliningSpan(span, document)); + return response.body + .map(span => this.convertOutliningSpan(span, document)) + .filter(foldingRange => !!foldingRange) as vscode.FoldingRange[]; } - private convertOutliningSpan(span: Proto.OutliningSpan, document: vscode.TextDocument): vscode.FoldingRange { + private convertOutliningSpan( + span: Proto.OutliningSpan, + document: vscode.TextDocument + ): vscode.FoldingRange | undefined { const range = typeConverters.Range.fromTextSpan(span.textSpan); const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span); + // Workaround for #49904 + if (span.kind === 'comment') { + const line = document.lineAt(range.start.line).text; + if (line.match(/\/\/\s*#endregion/gi)) { + return undefined; + } + } + const start = range.start.line; // workaround for #47240 const end = (range.end.character > 0 && document.getText(new vscode.Range(range.end.translate(0, -1), range.end)) === '}') @@ -51,8 +64,7 @@ export default class TypeScriptFoldingProvider implements vscode.FoldingRangePro } private static getFoldingRangeKind(span: Proto.OutliningSpan): vscode.FoldingRangeKind | undefined { - // TODO: remove cast once we get a new TS insiders - switch ((span as Proto.OutliningSpan & { kind: any }).kind) { + switch (span.kind) { case 'comment': return vscode.FoldingRangeKind.Comment; case 'region': return vscode.FoldingRangeKind.Region; case 'imports': return vscode.FoldingRangeKind.Imports; diff --git a/extensions/typescript-language-features/src/features/signatureHelpProvider.ts b/extensions/typescript-language-features/src/features/signatureHelpProvider.ts index 8bd9cbfe4a3..8644a1d9f62 100644 --- a/extensions/typescript-language-features/src/features/signatureHelpProvider.ts +++ b/extensions/typescript-language-features/src/features/signatureHelpProvider.ts @@ -3,23 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, ParameterInformation, Position, SignatureHelp, SignatureHelpProvider, SignatureInformation, TextDocument } from 'vscode'; +import * as vscode from 'vscode'; import * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import * as Previewer from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; -export default class TypeScriptSignatureHelpProvider implements SignatureHelpProvider { +export default class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider { + + public static readonly triggerCharacters = ['(', ',', '<']; public constructor( private readonly client: ITypeScriptServiceClient ) { } - public async provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken): Promise { + public async provideSignatureHelp( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Promise { const filepath = this.client.normalizePath(document.uri); if (!filepath) { - return null; + return undefined; } const args: Proto.SignatureHelpRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position); @@ -28,41 +34,40 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro const response = await this.client.execute('signatureHelp', args, token); info = response.body; if (!info) { - return null; + return undefined; } } catch { - return null; + return undefined; } - const result = new SignatureHelp(); + const result = new vscode.SignatureHelp(); result.activeSignature = info.selectedItemIndex; - result.activeParameter = info.argumentIndex; - - info.items.forEach((item, i) => { - // keep active parameter in bounds - if (i === info!.selectedItemIndex && item.isVariadic) { - result.activeParameter = Math.min(info!.argumentIndex, item.parameters.length - 1); - } - - const signature = new SignatureInformation(''); - signature.label += Previewer.plain(item.prefixDisplayParts); - - item.parameters.forEach((p, i, a) => { - const parameter = new ParameterInformation( - Previewer.plain(p.displayParts), - Previewer.markdownDocumentation(p.documentation, [])); - - signature.label += parameter.label; - signature.parameters.push(parameter); - if (i < a.length - 1) { - signature.label += Previewer.plain(item.separatorDisplayParts); - } - }); - signature.label += Previewer.plain(item.suffixDisplayParts); - signature.documentation = Previewer.markdownDocumentation(item.documentation, item.tags.filter(x => x.name !== 'param')); - result.signatures.push(signature); - }); + result.activeParameter = this.getActiveParmeter(info); + result.signatures = info.items.map(signature => this.convertSignature(signature)); return result; } -} \ No newline at end of file + + private getActiveParmeter(info: Proto.SignatureHelpItems): number { + const activeSignature = info.items[info.selectedItemIndex]; + if (activeSignature && activeSignature.isVariadic) { + return Math.min(info.argumentIndex, activeSignature.parameters.length - 1); + } + return info.argumentIndex; + } + + private convertSignature(item: Proto.SignatureHelpItem) { + const signature = new vscode.SignatureInformation( + Previewer.plain(item.prefixDisplayParts), + Previewer.markdownDocumentation(item.documentation, item.tags.filter(x => x.name !== 'param'))); + + signature.parameters = item.parameters.map(p => + new vscode.ParameterInformation( + Previewer.plain(p.displayParts), + Previewer.markdownDocumentation(p.documentation, []))); + + signature.label += signature.parameters.map(parameter => parameter.label).join(Previewer.plain(item.separatorDisplayParts)); + signature.label += Previewer.plain(item.suffixDisplayParts); + return signature; + } +} diff --git a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts index 791baff7567..fef27f1358a 100644 --- a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as Proto from '../protocol'; @@ -71,6 +72,7 @@ export class UpdateImportsOnFileRenameHandler { } // Make sure TS knows about file + this.bufferSyncSupport.closeResource(oldResource); this.bufferSyncSupport.openTextDocument(document); const edits = await this.getEditsForFileRename(document, oldFile, newFile); @@ -140,7 +142,7 @@ export class UpdateImportsOnFileRenameHandler { choice: Choice.Never, }, ], { - placeHolder: localize('prompt', "Update import paths?"), + placeHolder: localize('prompt', "Update import paths for moved file: '{0}'?", path.basename(newDocument.fileName)), ignoreFocusOut: true, }); diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 7faf5fd5216..b89714386ea 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -131,10 +131,13 @@ export default class LanguageProvider { this.disposables.push(languages.registerDocumentHighlightProvider(selector, new (await import('./features/documentHighlightProvider')).default(client))); this.disposables.push(languages.registerReferenceProvider(selector, new (await import('./features/referenceProvider')).default(client))); this.disposables.push(languages.registerDocumentSymbolProvider(selector, new (await import('./features/documentSymbolProvider')).default(client))); - this.disposables.push(languages.registerSignatureHelpProvider(selector, new (await import('./features/signatureHelpProvider')).default(client), '(', ',')); + + this.disposables.push(languages.registerRenameProvider(selector, new (await import('./features/renameProvider')).default(client))); this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/quickFixProvider')).default(client, this.fileConfigurationManager, commandManager, this.diagnosticsManager, this.bufferSyncSupport, this.telemetryReporter))); + const TypescriptSignatureHelpProvider = (await import('./features/signatureHelpProvider')).default; + this.disposables.push(languages.registerSignatureHelpProvider(selector, new TypescriptSignatureHelpProvider(client), ...TypescriptSignatureHelpProvider.triggerCharacters)); const refactorProvider = new (await import('./features/refactorProvider')).default(client, this.fileConfigurationManager, commandManager); this.disposables.push(languages.registerCodeActionsProvider(selector, refactorProvider, refactorProvider.metadata)); diff --git a/src/typings/chokidar.d.ts b/src/typings/chokidar.d.ts index 2aeacfc8db3..411080c2cc7 100644 --- a/src/typings/chokidar.d.ts +++ b/src/typings/chokidar.d.ts @@ -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 { diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts index 6538ee24941..5d2d82599e2 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/node/glob.test.ts @@ -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'); } }); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index f30d8844211..f1b2021abc3 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -716,7 +716,11 @@ export class WindowsManager implements IWindowsMainService { return window; } - private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: ICodeWindow): ICodeWindow { + private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: ICodeWindow): ICodeWindow { + if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { + windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587 + } + const browserWindow = this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, @@ -727,7 +731,7 @@ export class WindowsManager implements IWindowsMainService { filesToCreate, filesToDiff, filesToWait, - forceNewWindow: openInNewWindow, + forceNewWindow, windowToUse }); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 4002234ed86..8a6261c70e6 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -248,9 +248,8 @@ export interface IReportedExtension { malicious: boolean; } -export enum DownloadOperation { - None, - Install, +export enum InstallOperation { + Install = 1, Update } @@ -258,7 +257,7 @@ export interface IExtensionGalleryService { _serviceBrand: any; isEnabled(): boolean; query(options?: IQueryOptions): TPromise>; - download(extension: IGalleryExtension, operation: DownloadOperation): TPromise; + download(extension: IGalleryExtension, operation: InstallOperation): TPromise; reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise; getReadme(extension: IGalleryExtension): TPromise; getManifest(extension: IGalleryExtension): TPromise; @@ -276,6 +275,7 @@ export interface InstallExtensionEvent { export interface DidInstallExtensionEvent { identifier: IExtensionIdentifier; + operation: InstallOperation; zipPath?: string; gallery?: IGalleryExtension; local?: ILocalExtension; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 3d748b74240..b212007836e 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import { TPromise } from 'vs/base/common/winjs.base'; import { distinct } from 'vs/base/common/arrays'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; -import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, DownloadOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { assign, getOrDefault } from 'vs/base/common/objects'; import { IRequestService } from 'vs/platform/request/node/request'; @@ -473,7 +473,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - download(extension: IGalleryExtension, operation: DownloadOperation): TPromise { + download(extension: IGalleryExtension, operation: InstallOperation): TPromise { return this.loadCompatibleVersion(extension) .then(extension => { if (!extension) { @@ -492,7 +492,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { */ const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration })); - const operationParam = operation === DownloadOperation.Install ? 'install' : operation === DownloadOperation.Update ? 'update' : ''; + const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : ''; const downloadAsset = operationParam ? { uri: `${extension.assets.download.uri}&${operationParam}=true`, fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true` diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 1e2ae01434a..1e1bece31af 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -21,9 +21,9 @@ import { StatisticType, IExtensionIdentifier, IReportedExtension, - DownloadOperation + InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Limiter, always } from 'vs/base/common/async'; @@ -208,26 +208,29 @@ export class ExtensionManagementService extends Disposable implements IExtension } private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise { - return this.installExtension({ zipPath, id: identifier.id, metadata }) - .then(local => { - if (this.galleryService.isEnabled() && local.manifest.extensionDependencies && local.manifest.extensionDependencies.length) { - return this.getDependenciesToInstall(local.manifest.extensionDependencies) - .then(dependenciesToInstall => { - dependenciesToInstall = metadata ? dependenciesToInstall.filter(d => d.identifier.uuid !== metadata.id) : dependenciesToInstall; - return this.getInstalled() - .then(installed => this.downloadAndInstallExtensions(dependenciesToInstall, dependenciesToInstall.map(d => this.getOperation(d, installed)))); - }) - .then(() => local, error => { - this.setUninstalled(local); - return TPromise.wrapError(new Error(nls.localize('errorInstallingDependencies', "Error while installing dependencies. {0}", error instanceof Error ? error.message : error))); - }); - } - return local; - }) - .then( - local => { this._onDidInstallExtension.fire({ identifier, zipPath, local }); return local; }, - error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); } - ); + return this.getInstalled() + .then(installed => { + const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed); + return this.installExtension({ zipPath, id: identifier.id, metadata }) + .then(local => { + if (this.galleryService.isEnabled() && local.manifest.extensionDependencies && local.manifest.extensionDependencies.length) { + return this.getDependenciesToInstall(local.manifest.extensionDependencies) + .then(dependenciesToInstall => { + dependenciesToInstall = metadata ? dependenciesToInstall.filter(d => d.identifier.uuid !== metadata.id) : dependenciesToInstall; + return this.downloadAndInstallExtensions(dependenciesToInstall, dependenciesToInstall.map(d => this.getOperation(d.identifier, installed))); + }) + .then(() => local, error => { + this.setUninstalled(local); + return TPromise.wrapError(new Error(nls.localize('errorInstallingDependencies', "Error while installing dependencies. {0}", error instanceof Error ? error.message : error))); + }); + } + return local; + }) + .then( + local => { this._onDidInstallExtension.fire({ identifier, zipPath, local, operation }); return local; }, + error => { this._onDidInstallExtension.fire({ identifier, zipPath, operation, error }); return TPromise.wrapError(error); } + ); + }); } installFromGallery(extension: IGalleryExtension): TPromise { @@ -239,14 +242,14 @@ export class ExtensionManagementService extends Disposable implements IExtension if (extensionsToInstall.length > 1) { this.onInstallExtensions(extensionsToInstall.slice(1)); } - const operataions: DownloadOperation[] = extensionsToInstall.map(e => this.getOperation(e, installed)); + const operataions: InstallOperation[] = extensionsToInstall.map(e => this.getOperation(e.identifier, installed)); return this.downloadAndInstallExtensions(extensionsToInstall, operataions) .then( locals => this.onDidInstallExtensions(extensionsToInstall, locals, operataions, []) .then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])), errors => this.onDidInstallExtensions(extensionsToInstall, [], operataions, errors)); }, - error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension, installed)], [error]))); + error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension.identifier, installed)], [error]))); } reinstallFromGallery(extension: ILocalExtension): TPromise { @@ -266,8 +269,8 @@ export class ExtensionManagementService extends Disposable implements IExtension }); } - private getOperation(extensionToInstall: IGalleryExtension, installed: ILocalExtension[]): DownloadOperation { - return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall.identifier)) ? DownloadOperation.Update : DownloadOperation.Install; + private getOperation(extensionToInstall: IExtensionIdentifier, installed: ILocalExtension[]): InstallOperation { + return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall)) ? InstallOperation.Update : InstallOperation.Install; } private collectExtensionsToInstall(extension: IGalleryExtension): TPromise { @@ -284,12 +287,12 @@ export class ExtensionManagementService extends Disposable implements IExtension error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); } - private downloadAndInstallExtensions(extensions: IGalleryExtension[], operations: DownloadOperation[]): TPromise { + private downloadAndInstallExtensions(extensions: IGalleryExtension[], operations: InstallOperation[]): TPromise { return TPromise.join(extensions.map((extensionToInstall, index) => this.downloadAndInstallExtension(extensionToInstall, operations[index]))) .then(null, errors => this.rollback(extensions).then(() => TPromise.wrapError(errors), () => TPromise.wrapError(errors))); } - private downloadAndInstallExtension(extension: IGalleryExtension, operation: DownloadOperation): TPromise { + private downloadAndInstallExtension(extension: IGalleryExtension, operation: InstallOperation): TPromise { let installingExtension = this.installingExtensions.get(extension.identifier.id); if (!installingExtension) { installingExtension = this.getExtensionsReport() @@ -312,7 +315,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return installingExtension; } - private downloadInstallableExtension(extension: IGalleryExtension, operation: DownloadOperation): TPromise { + private downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): TPromise { const metadata = { id: extension.identifier.uuid, publisherId: extension.publisherId, @@ -351,21 +354,22 @@ export class ExtensionManagementService extends Disposable implements IExtension } } - private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], operations: DownloadOperation[], errors: Error[]): TPromise { + private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], operations: InstallOperation[], errors: Error[]): TPromise { extensions.forEach((gallery, index) => { const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid }; const local = locals[index]; const error = errors[index]; + const operation = operations[index]; if (local) { this.logService.info(`Extensions installed successfully:`, gallery.identifier.id); - this._onDidInstallExtension.fire({ identifier, gallery, local }); + this._onDidInstallExtension.fire({ identifier, gallery, local, operation }); } else { const errorCode = error && (error).code ? (error).code : ERROR_UNKNOWN; this.logService.error(`Failed to install extension:`, gallery.identifier.id, error ? error.message : errorCode); - this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode }); + this._onDidInstallExtension.fire({ identifier, gallery, operation, error: errorCode }); } const startTime = this.installationStartTime.get(gallery.identifier.id); - this.reportTelemetry(operations[index] === DownloadOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error); + this.reportTelemetry(operations[index] === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error); this.installationStartTime.delete(gallery.identifier.id); }); return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index bb161524a50..f548718cd75 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -151,7 +151,7 @@ export interface IWindowsService { toggleSharedProcess(): TPromise; // Global methods - openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise; + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise; openNewWindow(): TPromise; showWindow(windowId: number): TPromise; getWindows(): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]>; @@ -200,6 +200,7 @@ export interface IWindowService { getRecentlyOpened(): TPromise; focusWindow(): TPromise; closeWindow(): TPromise; + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise; isFocused(): TPromise; setDocumentEdited(flag: boolean): TPromise; onWindowTitleDoubleClick(): TPromise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index e6bd74f3dd9..86b8cf27dfd 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -52,7 +52,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'onWindowTitleDoubleClick', arg: number): TPromise; call(command: 'setDocumentEdited', arg: [number, boolean]): TPromise; call(command: 'quit'): TPromise; - call(command: 'openWindow', arg: [string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }]): TPromise; + call(command: 'openWindow', arg: [number, string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }]): TPromise; call(command: 'openNewWindow'): TPromise; call(command: 'showWindow', arg: number): TPromise; call(command: 'getWindows'): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]>; @@ -131,7 +131,7 @@ export class WindowsChannel implements IWindowsChannel { case 'unmaximizeWindow': return this.service.unmaximizeWindow(arg); case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg); case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); - case 'openWindow': return this.service.openWindow(arg[0], arg[1]); + case 'openWindow': return this.service.openWindow(arg[0], arg[1], arg[2]); case 'openNewWindow': return this.service.openNewWindow(); case 'showWindow': return this.service.showWindow(arg); case 'getWindows': return this.service.getWindows(); @@ -309,8 +309,8 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('toggleSharedProcess'); } - openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { - return this.channel.call('openWindow', [paths, options]); + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { + return this.channel.call('openWindow', [windowId, paths, options]); } openNewWindow(): TPromise { diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index fbc0aa9b39d..785106fbd91 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -85,6 +85,10 @@ export class WindowService implements IWindowService { return this.windowsService.saveAndEnterWorkspace(this.windowId, path); } + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise { + return this.windowsService.openWindow(this.windowId, paths, options); + } + closeWindow(): TPromise { return this.windowsService.closeWindow(this.windowId); } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 8e3877562be..d912fb80627 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -119,6 +119,7 @@ export interface IWindowsMainService { export interface IOpenConfiguration { context: OpenContext; + contextWindowId?: number; cli: ParsedArgs; userEnv?: IProcessEnvironment; pathsToOpen?: string[]; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 24c7924ff9a..ebc2d68f0d6 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -360,7 +360,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(null); } - openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { this.logService.trace('windowsService#openWindow'); if (!paths || !paths.length) { return TPromise.as(null); @@ -368,6 +368,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable this.windowsMainService.open({ context: OpenContext.API, + contextWindowId: windowId, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options && options.forceNewWindow, diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index f05c8c05523..9199efd1eec 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1886,6 +1886,9 @@ declare module 'vscode' { * Kind of a code action. * * Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`. + * + * Code action kinds are used by VS Code for UI elements such as the refactoring context menu. Users + * can also trigger code actions with a specific kind with the `editor.action.codeAction` command. */ export class CodeActionKind { /** @@ -1894,12 +1897,16 @@ declare module 'vscode' { static readonly Empty: CodeActionKind; /** - * Base kind for quickfix actions: `quickfix` + * Base kind for quickfix actions: `quickfix`. + * + * Quick fix actions address a problem in the code and are shown in the normal code action context menu. */ static readonly QuickFix: CodeActionKind; /** * Base kind for refactoring actions: `refactor` + * + * Refactoring actions are shown in the refactoring context menu. */ static readonly Refactor: CodeActionKind; @@ -1945,12 +1952,13 @@ declare module 'vscode' { /** * Base kind for source actions: `source` * - * Source code actions apply to the entire file. + * Source code actions apply to the entire file and can be run on save + * using `editor.codeActionsOnSave`. They also are shown in `source` context menu. */ static readonly Source: CodeActionKind; /** - * Base kind for an organize imports source action: `source.organizeImports` + * Base kind for an organize imports source action: `source.organizeImports`. */ static readonly SourceOrganizeImports: CodeActionKind; diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index d5d5a6501fe..c332e599c2f 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -362,7 +362,7 @@ class ExtHostTreeView extends Disposable { return item; } - private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode, first?: boolean): TreeItemHandle { + private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode, returnFirst?: boolean): TreeItemHandle { if (id) { return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`; } @@ -373,14 +373,20 @@ class ExtHostTreeView extends Disposable { const existingHandle = this.nodes.has(element) ? this.nodes.get(element).item.handle : void 0; const childrenNodes = (this.getChildrenNodes(parent) || []); - for (let counter = 0; counter <= childrenNodes.length; counter++) { - const handle = `${prefix}/${counter}:${elementId}`; - if (first || !this.elements.has(handle) || existingHandle === handle) { - return handle; + let handle: TreeItemHandle; + let counter = 0; + do { + handle = `${prefix}/${counter}:${elementId}`; + if (returnFirst || !this.elements.has(handle) || existingHandle === handle) { + // Return first if asked for or + // Return if handle does not exist or + // Return if handle is being reused + break; } - } + counter++; + } while (counter <= childrenNodes.length); - throw new Error('This should not be reached'); + return handle; } private getLightIconPath(extensionTreeItem: vscode.TreeItem): string { @@ -457,7 +463,7 @@ class ExtHostTreeView extends Disposable { } } } - node.children = []; + node.children = void 0; } else { this.clearAll(); } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 46453e9343d..a916997520c 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; @@ -228,7 +228,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { label: string, @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IWindowsService private windowsService: IWindowsService, + @IWindowService private windowService: IWindowService, @IWorkspacesService private workspacesService: IWorkspacesService ) { super(id, label); @@ -239,7 +239,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { return this.workspacesService.createWorkspace(folders).then(newWorkspace => { return this.workspaceEditingService.copyWorkspaceSettings(newWorkspace).then(() => { - return this.windowsService.openWindow([newWorkspace.configPath], { forceNewWindow: true }); + return this.windowService.openWindow([newWorkspace.configPath], { forceNewWindow: true }); }); }); } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 8a3a4a62cfc..aff0742f1ce 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -304,7 +304,7 @@ export class ResourcesDropHandler { // Open workspacesToOpen.then(workspaces => { - this.windowsService.openWindow(workspaces, { forceReuseWindow: true }); + this.windowService.openWindow(workspaces, { forceReuseWindow: true }); }); return true; diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index d27b8209c2b..d6640decdb5 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -314,10 +314,6 @@ export class NotificationViewItemProgress implements INotificationViewItemProgre } public worked(value: number): void { - if (this._state.worked === value) { - return; - } - this._state.worked = value; this._state.infinite = void 0; diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 0bc919521c3..9ae665088ad 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -702,7 +702,6 @@ export abstract class BaseOpenRecentAction extends Action { constructor( id: string, label: string, - private windowsService: IWindowsService, private windowService: IWindowService, private quickOpenService: IQuickOpenService, private contextService: IWorkspaceContextService, @@ -757,7 +756,7 @@ export abstract class BaseOpenRecentAction extends Action { const runPick = (path: string, isFile: boolean, context: IEntryRunContext) => { const forceNewWindow = context.keymods.ctrlCmd; - this.windowsService.openWindow([path], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); + this.windowService.openWindow([path], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); }; const workspacePicks: IFilePickOpenEntry[] = recentWorkspaces.map((workspace, index) => toPick(workspace, index === 0 ? { label: nls.localize('workspaces', "workspaces") } : void 0, isSingleFolderWorkspaceIdentifier(workspace) ? FileKind.FOLDER : FileKind.ROOT_FOLDER, this.environmentService, !this.isQuickNavigate() ? this.removeAction : void 0)); @@ -812,7 +811,6 @@ export class OpenRecentAction extends BaseOpenRecentAction { constructor( id: string, label: string, - @IWindowsService windowsService: IWindowsService, @IWindowService windowService: IWindowService, @IQuickOpenService quickOpenService: IQuickOpenService, @IWorkspaceContextService contextService: IWorkspaceContextService, @@ -820,7 +818,7 @@ export class OpenRecentAction extends BaseOpenRecentAction { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, windowsService, windowService, quickOpenService, contextService, environmentService, keybindingService, instantiationService); + super(id, label, windowService, quickOpenService, contextService, environmentService, keybindingService, instantiationService); } protected isQuickNavigate(): boolean { @@ -836,7 +834,6 @@ export class QuickOpenRecentAction extends BaseOpenRecentAction { constructor( id: string, label: string, - @IWindowsService windowsService: IWindowsService, @IWindowService windowService: IWindowService, @IQuickOpenService quickOpenService: IQuickOpenService, @IWorkspaceContextService contextService: IWorkspaceContextService, @@ -844,7 +841,7 @@ export class QuickOpenRecentAction extends BaseOpenRecentAction { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, windowsService, windowService, quickOpenService, contextService, environmentService, keybindingService, instantiationService); + super(id, label, windowService, quickOpenService, contextService, environmentService, keybindingService, instantiationService); } protected isQuickNavigate(): boolean { diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 17413801f78..dbb1457fbdb 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -14,7 +14,7 @@ import * as ExtensionsActions from 'vs/workbench/parts/extensions/browser/extens import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService'; @@ -228,7 +228,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = paged.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); assert.equal('Uninstall', testObject.label); @@ -452,7 +452,7 @@ suite('ExtensionsActions Test', () => { .then(page => { testObject.extension = page.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); assert.equal('extension-action manage', testObject.class); @@ -991,7 +991,7 @@ suite('ExtensionsActions Test', () => { .then((paged) => { testObject.extension = paged.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); assert.equal('Reload to activate', testObject.tooltip); @@ -1009,7 +1009,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = paged.firstPage[0]; const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version) }; installEvent.fire({ identifier: identifier, gallery }); - didInstallEvent.fire({ identifier: identifier, gallery, local: aLocalExtension('a', gallery, { identifier }) }); + didInstallEvent.fire({ identifier: identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, { identifier }) }); uninstallEvent.fire(identifier); didUninstallEvent.fire({ identifier: identifier }); @@ -1048,7 +1048,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); const id = getLocalExtensionIdFromGallery(gallery, gallery.version); installEvent.fire({ identifier: { id }, gallery }); - didInstallEvent.fire({ identifier: { id }, gallery, local }); + didInstallEvent.fire({ identifier: { id }, gallery, operation: InstallOperation.Install, local }); assert.ok(!testObject.enabled); }); @@ -1066,7 +1066,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a', { uuid: local.identifier.id, version: '1.0.2' }); installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); assert.equal('Reload to update', testObject.tooltip); @@ -1088,7 +1088,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(!testObject.enabled); }); @@ -1180,7 +1180,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension('a', gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); return workbenchService.setEnablement(extensions[0], EnablementState.Enabled) .then(() => { assert.ok(testObject.enabled); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 8d9e45be1f5..2789c6b4322 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -15,7 +15,7 @@ import { IExtensionsWorkbenchService, ExtensionState } from 'vs/workbench/parts/ import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService'; @@ -334,7 +334,7 @@ suite('ExtensionsWorkbenchService Test', () => { assert.equal(ExtensionState.Installing, actual.state); // Installed - didInstallEvent.fire({ identifier, gallery, local: aLocalExtension(gallery.name, gallery, { identifier }) }); + didInstallEvent.fire({ identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, { identifier }) }); assert.equal(ExtensionState.Installed, actual.state); assert.equal(1, testObject.local.length); @@ -410,7 +410,7 @@ suite('ExtensionsWorkbenchService Test', () => { testObject.onChange(target); // Installed - didInstallEvent.fire({ identifier: gallery.identifier, gallery, local: aLocalExtension(gallery.name, gallery, gallery) }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, gallery) }); assert.ok(target.calledOnce); }); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 5b195cdb8a1..e84dd3cded2 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -25,7 +25,8 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID } from 'vs/workbench/parts/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { toResource } from 'vs/workbench/common/editor'; +import { IUntitledResourceInput } from 'vs/platform/editor/common/editor'; import { ExplorerItem, Model, NewStatPlaceholder } from 'vs/workbench/parts/files/common/explorerModel'; import { ExplorerView } from 'vs/workbench/parts/files/electron-browser/views/explorerView'; import { ExplorerViewlet } from 'vs/workbench/parts/files/electron-browser/explorerViewlet'; @@ -36,7 +37,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor, IConstructorSignature2 } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/parts/files/electron-browser/fileCommands'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -1383,9 +1384,9 @@ export class ShowOpenedFileInNewWindow extends Action { constructor( id: string, label: string, - @IWindowsService private windowsService: IWindowsService, @IEditorService private editorService: IEditorService, - @INotificationService private notificationService: INotificationService, + @IWindowService private windowService: IWindowService, + @INotificationService private notificationService: INotificationService ) { super(id, label); } @@ -1393,7 +1394,7 @@ export class ShowOpenedFileInNewWindow extends Action { public run(): TPromise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: Schemas.file /* todo@remote */ }); if (fileResource) { - this.windowsService.openWindow([fileResource.fsPath], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); + this.windowService.openWindow([fileResource.fsPath], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); } else { this.notificationService.info(nls.localize('openFileToShowInNewWindow', "Open a file first to open in new window")); } diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts index 057ca1600e4..199d769c59d 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts @@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as labels from 'vs/base/common/labels'; import URI from 'vs/base/common/uri'; import { toResource, IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -77,8 +77,9 @@ export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); export const openWindowCommand = (accessor: ServicesAccessor, paths: string[], forceNewWindow: boolean) => { - const windowsService = accessor.get(IWindowsService); - windowsService.openWindow(paths, { forceNewWindow }); + const windowService = accessor.get(IWindowService); + + windowService.openWindow(paths, { forceNewWindow }); }; function save(resource: URI, isSaveAs: boolean, editorService: IEditorService, fileService: IFileService, untitledEditorService: IUntitledEditorService, diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index ff59ed77adf..7e9a9f3ffe4 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -15,7 +15,7 @@ import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/exte import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { IExtensionManagementService, DidInstallExtensionEvent, LocalExtensionType, IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, DidInstallExtensionEvent, LocalExtensionType, IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -68,7 +68,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo private onDidInstallExtension(e: DidInstallExtensionEvent): void { const donotAskUpdateKey = 'langugage.update.donotask'; - if (!this.storageService.getBoolean(donotAskUpdateKey) && e.local && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { + if (!this.storageService.getBoolean(donotAskUpdateKey) && e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { const locale = e.local.manifest.contributes.localizations[0].languageId; if (platform.language !== locale) { this.notificationService.prompt( diff --git a/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts index 237f4d4efa9..2c0ca2a7e8e 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts @@ -132,11 +132,12 @@ class KeybindingInputWidget extends Widget { export class DefineKeybindingWidget extends Widget { private static readonly WIDTH = 400; - private static readonly HEIGHT = 90; + private static readonly HEIGHT = 110; private _domNode: FastDomNode; private _keybindingInputWidget: KeybindingInputWidget; private _outputNode: HTMLElement; + private _showExistingKeybindingsNode: HTMLElement; private _firstPart: ResolvedKeybinding = null; private _chordPart: ResolvedKeybinding = null; @@ -144,6 +145,12 @@ export class DefineKeybindingWidget extends Widget { private _onHide = this._register(new Emitter()); + private _onDidChange = this._register(new Emitter()); + public onDidChange: Event = this._onDidChange.event; + + private _onShowExistingKeybindings = this._register(new Emitter()); + public readonly onShowExistingKeybidings: Event = this._onShowExistingKeybindings.event; + constructor( parent: HTMLElement, @IInstantiationService private instantiationService: IInstantiationService, @@ -171,18 +178,11 @@ export class DefineKeybindingWidget extends Widget { this._chordPart = null; this._keybindingInputWidget.setInputValue(''); dom.clearNode(this._outputNode); + dom.clearNode(this._showExistingKeybindingsNode); this._keybindingInputWidget.focus(); } const disposable = this._onHide.event(() => { - if (this._firstPart) { - let r = this._firstPart.getUserSettingsLabel(); - if (this._chordPart) { - r = r + ' ' + this._chordPart.getUserSettingsLabel(); - } - c(r); - } else { - c(null); - } + c(this.getUserSettingsLabel()); disposable.dispose(); }); }); @@ -196,6 +196,21 @@ export class DefineKeybindingWidget extends Widget { this._domNode.setLeft(left); } + printExisting(numberOfExisting: number): void { + if (numberOfExisting > 0) { + let outputString: string = nls.localize('defineKeybinding.existing', "Existing"); + outputString = numberOfExisting + ' ' + outputString; + let textNode = document.createTextNode(outputString); + let textSpan = document.createElement('span'); + dom.addClass(textSpan, 'existingText'); + textSpan.appendChild(textNode); + this._showExistingKeybindingsNode.appendChild(textSpan); + textSpan.onmousedown = (e) => { e.preventDefault(); }; + textSpan.onmouseup = (e) => { e.preventDefault(); }; + textSpan.onclick = () => { this._onShowExistingKeybindings.fire(this.getUserSettingsLabel()); }; + } + } + private create(): void { this._domNode = createFastDomNode(document.createElement('div')); this._domNode.setDisplay('none'); @@ -219,24 +234,41 @@ export class DefineKeybindingWidget extends Widget { })); this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingInputWidget, this._domNode.domNode, {})); - this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.printKeybinding(keybinding))); + this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); this._register(this._keybindingInputWidget.onEnter(() => this.hide())); this._register(this._keybindingInputWidget.onEscape(() => this.onCancel())); this._register(this._keybindingInputWidget.onBlur(() => this.onCancel())); this._outputNode = dom.append(this._domNode.domNode, dom.$('.output')); + this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing')); } - private printKeybinding(keybinding: [ResolvedKeybinding, ResolvedKeybinding]): void { + private onKeybinding(keybinding: [ResolvedKeybinding, ResolvedKeybinding]): void { const [firstPart, chordPart] = keybinding; this._firstPart = firstPart; this._chordPart = chordPart; dom.clearNode(this._outputNode); + dom.clearNode(this._showExistingKeybindingsNode); new KeybindingLabel(this._outputNode, OS).set(this._firstPart, null); if (this._chordPart) { this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to"))); new KeybindingLabel(this._outputNode, OS).set(this._chordPart, null); } + const label = this.getUserSettingsLabel(); + if (label) { + this._onDidChange.fire(label); + } + } + + private getUserSettingsLabel(): string { + let label = null; + if (this._firstPart) { + label = this._firstPart.getUserSettingsLabel(); + if (this._chordPart) { + label = label + ' ' + this._chordPart.getUserSettingsLabel(); + } + } + return label; } private onCancel(): void { diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts index ed28ed4d36a..52c75f39466 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts @@ -270,6 +270,8 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.overlayContainer.style.position = 'absolute'; this.overlayContainer.style.zIndex = '10'; this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer)); + this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel.fetch(`"${keybindingStr}"`).length))); + this._register(this.defineKeybindingWidget.onShowExistingKeybidings(keybindingStr => this.searchWidget.setValue(`"${keybindingStr}"`))); this.hideOverlayContainer(); } diff --git a/src/vs/workbench/parts/preferences/browser/media/keybindings.css b/src/vs/workbench/parts/preferences/browser/media/keybindings.css index 9e70ed793fb..e37b5c3770c 100644 --- a/src/vs/workbench/parts/preferences/browser/media/keybindings.css +++ b/src/vs/workbench/parts/preferences/browser/media/keybindings.css @@ -14,7 +14,8 @@ } .defineKeybindingWidget .monaco-inputbox, -.defineKeybindingWidget .output { +.defineKeybindingWidget .output, +.defineKeybindingWidget .existing { margin-top:10px; width: 400px; display: block; @@ -30,6 +31,11 @@ justify-content: center; } +.defineKeybindingWidget .existing .existingText { + text-decoration: underline; + cursor: pointer; +} + .defineKeybindingWidget .output .monaco-keybinding { margin: 0px 4px; } @@ -63,4 +69,4 @@ .monaco-editor .keybindingError { box-shadow: inset 0 0 0 1px #B9B9B9; background-color: rgba(250, 100, 100, 0.2); -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index f34a233233f..395c614d1d2 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -7,7 +7,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Delayer } from 'vs/base/common/async'; import * as arrays from 'vs/base/common/arrays'; -import * as strings from 'vs/base/common/strings'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { IAction } from 'vs/base/common/actions'; @@ -595,8 +594,7 @@ export class FeedbackWidgetRenderer extends Disposable { @IEditorService private editorService: IEditorService, @ITelemetryService private telemetryService: ITelemetryService, @INotificationService private notificationService: INotificationService, - @IEnvironmentService private environmentService: IEnvironmentService, - @IConfigurationService private configurationService: IConfigurationService + @IEnvironmentService private environmentService: IEnvironmentService ) { super(); } @@ -679,86 +677,90 @@ export class FeedbackWidgetRenderer extends Disposable { } private sendFeedback(feedbackEditor: ICodeEditor, result: IFilterResult, scoredResults: IScoredResults): TPromise { - const model = feedbackEditor.getModel(); - const expectedQueryLines = model.getLinesContent() - .filter(line => !strings.startsWith(line, '//')); + // const model = feedbackEditor.getModel(); + // const expectedQueryLines = model.getLinesContent() + // .filter(line => !strings.startsWith(line, '//')); - let expectedQuery: any; - try { - expectedQuery = JSON.parse(expectedQueryLines.join('\n')); - } catch (e) { - // invalid JSON - return TPromise.wrapError(new Error('Invalid JSON: ' + e.message)); - } + // let expectedQuery: any; + // try { + // expectedQuery = JSON.parse(expectedQueryLines.join('\n')); + // } catch (e) { + // // invalid JSON + // return TPromise.wrapError(new Error('Invalid JSON: ' + e.message)); + // } - const userComment = expectedQuery.comment === FeedbackWidgetRenderer.DEFAULT_COMMENT_TEXT ? undefined : expectedQuery.comment; + // const userComment = expectedQuery.comment === FeedbackWidgetRenderer.DEFAULT_COMMENT_TEXT ? undefined : expectedQuery.comment; - // validate alts - if (!this.validateAlts(expectedQuery.alts)) { - return TPromise.wrapError(new Error('alts must be an array of 2-element string arrays')); - } + // // validate alts + // if (!this.validateAlts(expectedQuery.alts)) { + // return TPromise.wrapError(new Error('alts must be an array of 2-element string arrays')); + // } - const altsAdded = expectedQuery.alts && expectedQuery.alts.length; - const alts = altsAdded ? expectedQuery.alts : undefined; - const workbenchSettings = this.configurationService.getValue().workbench.settings; - const autoIngest = workbenchSettings.naturalLanguageSearchAutoIngestFeedback; + // const altsAdded = expectedQuery.alts && expectedQuery.alts.length; + // const alts = altsAdded ? expectedQuery.alts : undefined; + // const workbenchSettings = this.configurationService.getValue().workbench.settings; + // const autoIngest = workbenchSettings.naturalLanguageSearchAutoIngestFeedback; - const nlpMetadata = result.metadata && result.metadata['nlpResult']; - const duration = nlpMetadata && nlpMetadata.duration; - const requestBody = nlpMetadata && nlpMetadata.requestBody; + // const nlpMetadata = result.metadata && result.metadata['nlpResult']; + // const duration = nlpMetadata && nlpMetadata.duration; + // const requestBody = nlpMetadata && nlpMetadata.requestBody; - const actualResultScores = {}; - for (let key in scoredResults) { - actualResultScores[key] = { - score: scoredResults[key].score - }; - } + // const actualResultScores = {}; + // for (let key in scoredResults) { + // actualResultScores[key] = { + // score: scoredResults[key].score + // }; + // } - /* __GDPR__ - "settingsSearchResultFeedback" : { - "query" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, - "requestBody" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, - "userComment" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, - "actualResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "expectedResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "buildNumber" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "alts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "autoIngest" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - return this.telemetryService.publicLog('settingsSearchResultFeedback', { - query: result.query, - requestBody, - userComment, - actualResults: actualResultScores, - expectedResults: expectedQuery.resultScores, - duration, - buildNumber: this.environmentService.settingsSearchBuildId, - alts, - autoIngest - }); + // /* __GDPR__ + // "settingsSearchResultFeedback" : { + // "query" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, + // "requestBody" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, + // "userComment" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, + // "actualResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + // "expectedResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + // "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + // "buildNumber" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + // "alts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + // "autoIngest" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + // } + // */ + // return this.telemetryService.publicLog('settingsSearchResultFeedback', { + // query: result.query, + // requestBody, + // userComment, + // actualResults: actualResultScores, + // expectedResults: expectedQuery.resultScores, + // duration, + // buildNumber: this.environmentService.settingsSearchBuildId, + // alts, + // autoIngest + // }); + + // TODO@roblou - reduce GDPR-relevant telemetry by removing this, but it's still helpful for personal use. + // Consider changing this to write to disk. + return TPromise.wrap(null); } - private validateAlts(alts?: string[][]): boolean { - if (!alts) { - return true; - } + // private validateAlts(alts?: string[][]): boolean { + // if (!alts) { + // return true; + // } - if (!Array.isArray(alts)) { - return false; - } + // if (!Array.isArray(alts)) { + // return false; + // } - if (!alts.length) { - return true; - } + // if (!alts.length) { + // return true; + // } - if (!alts.every(altPair => Array.isArray(altPair) && altPair.length === 2 && typeof altPair[0] === 'string' && typeof altPair[1] === 'string')) { - return false; - } + // if (!alts.every(altPair => Array.isArray(altPair) && altPair.length === 2 && typeof altPair[0] === 'string' && typeof altPair[1] === 'string')) { + // return false; + // } - return true; - } + // return true; + // } private disposeWidget(): void { if (this._feedbackWidget) { diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index d0a998f9117..46b58348484 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -359,8 +359,10 @@ function splitGlobPattern(pattern: string): string[] { * Note - we used {} here previously but ripgrep can't handle nested {} patterns. See https://github.com/Microsoft/vscode/issues/32761 */ function expandGlobalGlob(pattern: string): string[] { - return [ + const patterns = [ `**/${pattern}/**`, `**/${pattern}` ]; + + return patterns.map(p => p.replace(/\*\*\/\*\*/g, '**')); } diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 212ef51c2be..bbba914a80f 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -600,6 +600,7 @@ configurationRegistry.registerConfiguration({ included: platform.isMacintosh }, 'search.location': { + type: 'string', enum: ['sidebar', 'panel'], default: 'sidebar', description: nls.localize('search.location', "Controls if the search will be shown as a view in the sidebar or as a panel in the panel area for more horizontal space. Next release search in panel will have improved horizontal layout and this will no longer be a preview."), diff --git a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts index 3d155ffe880..cc44af398e8 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts @@ -7,19 +7,16 @@ import * as assert from 'assert'; import { IExpression } from 'vs/base/common/glob'; import * as paths from 'vs/base/common/paths'; -import * as arrays from 'vs/base/common/arrays'; import uri from 'vs/base/common/uri'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceContextService, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { QueryBuilder, ISearchPathsResult } from 'vs/workbench/parts/search/common/queryBuilder'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IFolderQuery, IPatternInfo, ISearchQuery, QueryType } from 'vs/platform/search/common/search'; +import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace'; +import { ISearchPathsResult, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; - -import { ISearchQuery, QueryType, IPatternInfo, IFolderQuery } from 'vs/platform/search/common/search'; - const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true }; const DEFAULT_QUERY_PROPS = { useRipgrep: true, disregardIgnoreFiles: false }; @@ -342,18 +339,18 @@ suite('QueryBuilder', () => { assert.deepEqual( queryBuilder.parseSearchPaths(includePattern), { - pattern: patternsToIExpression(...arrays.flatten(expectedPatterns.map(globalGlob))) + pattern: patternsToIExpression(...expectedPatterns) }, includePattern); } [ - ['a', ['a']], - ['a/b', ['a/b']], - ['a/b, c', ['a/b', 'c']], - ['a,.txt', ['a', '*.txt']], - ['a,,,b', ['a', 'b']], - ['**/a,b/**', ['**/a', 'b/**']] + ['a', ['**/a/**', '**/a']], + ['a/b', ['**/a/b', '**/a/b/**']], + ['a/b, c', ['**/a/b', '**/c', '**/a/b/**', '**/c/**']], + ['a,.txt', ['**/a', '**/a/**', '**/*.txt', '**/*.txt/**']], + ['a,,,b', ['**/a', '**/a/**', '**/b', '**/b/**']], + ['**/a,b/**', ['**/a', '**/a/**', '**/b/**']] ].forEach(([includePattern, expectedPatterns]) => testSimpleIncludes(includePattern, expectedPatterns)); }); diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index 91a808bc367..f8690874c6d 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -125,25 +125,25 @@ suite('SearchModel', () => { assert.deepEqual(['searchResultsFirstRender', { duration: -1 }], data); }); - test('Search Model: Search reports timed telemetry on search when progress is not called', () => { - let target2 = sinon.spy(); - stub(nullEvent, 'stop', target2); - let target1 = sinon.stub().returns(nullEvent); - instantiationService.stub(ITelemetryService, 'publicLog', target1); + // test('Search Model: Search reports timed telemetry on search when progress is not called', () => { + // let target2 = sinon.spy(); + // stub(nullEvent, 'stop', target2); + // let target1 = sinon.stub().returns(nullEvent); + // instantiationService.stub(ITelemetryService, 'publicLog', target1); - instantiationService.stub(ISearchService, 'search', ppromiseWithProgress([])); + // instantiationService.stub(ISearchService, 'search', ppromiseWithProgress([])); - let testObject = instantiationService.createInstance(SearchModel); - const result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries }); + // let testObject = instantiationService.createInstance(SearchModel); + // const result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries }); - return timeout(1).then(() => { - return result.then(() => { - assert.ok(target1.calledWith('searchResultsFirstRender')); - assert.ok(target1.calledWith('searchResultsFinished')); - }); + // return timeout(1).then(() => { + // return result.then(() => { + // assert.ok(target1.calledWith('searchResultsFirstRender')); + // assert.ok(target1.calledWith('searchResultsFinished')); + // }); - }); - }); + // }); + // }); test('Search Model: Search reports timed telemetry on search when progress is called', () => { let target2 = sinon.spy(); diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 2a6e3695400..451f158eead 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -13,7 +13,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -220,7 +220,6 @@ class WelcomePage { @IEditorService private editorService: IEditorService, @IInstantiationService private instantiationService: IInstantiationService, @IWindowService private windowService: IWindowService, - @IWindowsService private windowsService: IWindowsService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IConfigurationService private configurationService: IConfigurationService, @IEnvironmentService private environmentService: IEnvironmentService, @@ -316,7 +315,7 @@ class WelcomePage { id: 'openRecentFolder', from: telemetryFrom }); - this.windowsService.openWindow([wsPath], { forceNewWindow: e.ctrlKey || e.metaKey }); + this.windowService.openWindow([wsPath], { forceNewWindow: e.ctrlKey || e.metaKey }); e.preventDefault(); e.stopPropagation(); }); diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index eba4967b3ef..cacc1dc7a68 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -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()); } } diff --git a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index fc92f0581cb..550297d06f0 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts @@ -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; + 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 { + this._options = options; + this._watchers = Object.create(null); + this._watcherCount = 0; + this._watcherPromise = new TPromise((c, e, p) => { + this._errorCallback = (error) => { + this.stop(); + e(error); + }; + this._fileChangeCallback = p; + }, () => { + this.stop(); + }); + return this._watcherPromise; + } + + public setRoots(requests: IWatcherRequest[]): TPromise { + 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 { 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((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 ((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 ((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 { - 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; } diff --git a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts new file mode 100644 index 00000000000..9f9ce6309c7 --- /dev/null +++ b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -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(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.skip('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 }]); + }); + +}); + diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcher.ts b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts index 6961b77f6a2..8ed35e6c792 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts @@ -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; + initialize(options: IWatcherOptions): TPromise; + setRoots(roots: IWatcherRequest[]): TPromise; } diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index 9fcd515708f..bf2fba4664b 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -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; + call(command: 'initialize', options: IWatcherOptions): TPromise; + call(command: 'setRoots', request: IWatcherRequest[]): TPromise; call(command: string, arg: any): TPromise; } @@ -20,7 +21,8 @@ export class WatcherChannel implements IWatcherChannel { call(command: string, arg: any): TPromise { 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 { - return this.channel.call('watch', request); + initialize(options: IWatcherOptions): TPromise { + return this.channel.call('initialize', options); + } + + setRoots(roots: IWatcherRequest[]): TPromise { + return this.channel.call('setRoots', roots); } } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts index 160d25da051..e51c287854f 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -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('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({ + 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); + } } \ No newline at end of file diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index a84830b74de..89d63a217ae 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1025,6 +1025,10 @@ export class TestWindowService implements IWindowService { return TPromise.as(void 0); } + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { + return TPromise.as(void 0); + } + closeWindow(): TPromise { return TPromise.as(void 0); } @@ -1212,7 +1216,7 @@ export class TestWindowsService implements IWindowsService { } // Global methods - openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { return TPromise.as(void 0); } diff --git a/test/smoke/src/areas/css/css.test.ts b/test/smoke/src/areas/css/css.test.ts index fa27427919f..33dc79bc1c3 100644 --- a/test/smoke/src/areas/css/css.test.ts +++ b/test/smoke/src/areas/css/css.test.ts @@ -28,7 +28,7 @@ export function setup() { await app.workbench.problems.hideProblemsView(); }); - it.skip('verifies that warning becomes an error once setting changed', async function () { + it('verifies that warning becomes an error once setting changed', async function () { // settings might take a while to update? this.timeout(40000); diff --git a/test/smoke/src/areas/debug/debug.test.ts b/test/smoke/src/areas/debug/debug.test.ts index b193d856cfa..2be739d4061 100644 --- a/test/smoke/src/areas/debug/debug.test.ts +++ b/test/smoke/src/areas/debug/debug.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as http from 'http'; +// import * as http from 'http'; import * as path from 'path'; import * as fs from 'fs'; import * as stripJsonComments from 'strip-json-comments'; @@ -43,69 +43,69 @@ export function setup() { await app.workbench.debug.setBreakpointOnLine(6); }); - let port: number; - it('start debugging', async function () { - const app = this.app as Application; + // let port: number; + // it('start debugging', async function () { + // const app = this.app as Application; - // TODO@isidor - await new Promise(c => setTimeout(c, 100)); + // // TODO@isidor + // await new Promise(c => setTimeout(c, 100)); - port = await app.workbench.debug.startDebugging(); + // port = await app.workbench.debug.startDebugging(); - await new Promise((c, e) => { - const request = http.get(`http://localhost:${port}`); - request.on('error', e); - app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, 'looking for index.js and line 6').then(c, e); - }); - }); + // await new Promise((c, e) => { + // const request = http.get(`http://localhost:${port}`); + // request.on('error', e); + // app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, 'looking for index.js and line 6').then(c, e); + // }); + // }); - it('focus stack frames and variables', async function () { - const app = this.app as Application; + // it('focus stack frames and variables', async function () { + // const app = this.app as Application; - await app.workbench.debug.waitForVariableCount(4); + // await app.workbench.debug.waitForVariableCount(4); - await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js'); - await app.workbench.debug.waitForVariableCount(5); + // await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js'); + // await app.workbench.debug.waitForVariableCount(5); - await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js'); - await app.workbench.debug.waitForVariableCount(3); + // await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js'); + // await app.workbench.debug.waitForVariableCount(3); - await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js'); - await app.workbench.debug.waitForVariableCount(4); - }); + // await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js'); + // await app.workbench.debug.waitForVariableCount(4); + // }); - it('stepOver, stepIn, stepOut', async function () { - const app = this.app as Application; + // it('stepOver, stepIn, stepOut', async function () { + // const app = this.app as Application; - await app.workbench.debug.stepIn(); + // await app.workbench.debug.stepIn(); - const first = await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js', 'looking for response.js'); - await app.workbench.debug.stepOver(); + // const first = await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js', 'looking for response.js'); + // await app.workbench.debug.stepOver(); - await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js' && sf.lineNumber === first.lineNumber + 1, `looking for response.js and line ${first.lineNumber + 1}`); - await app.workbench.debug.stepOut(); + // await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js' && sf.lineNumber === first.lineNumber + 1, `looking for response.js and line ${first.lineNumber + 1}`); + // await app.workbench.debug.stepOut(); - await app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 7, `looking for index.js and line 7`); - }); + // await app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 7, `looking for index.js and line 7`); + // }); - it('continue', async function () { - const app = this.app as Application; + // it('continue', async function () { + // const app = this.app as Application; - await app.workbench.debug.continue(); + // await app.workbench.debug.continue(); - await new Promise((c, e) => { - const request = http.get(`http://localhost:${port}`); - request.on('error', e); - app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, `looking for index.js and line 6`).then(c, e); - }); + // await new Promise((c, e) => { + // const request = http.get(`http://localhost:${port}`); + // request.on('error', e); + // app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, `looking for index.js and line 6`).then(c, e); + // }); - }); + // }); - it('debug console', async function () { - const app = this.app as Application; + // it('debug console', async function () { + // const app = this.app as Application; - await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4'); - }); + // await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4'); + // }); it('stop debugging', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index b5de701993f..1b2bbbec548 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -7,7 +7,7 @@ import { Application } from '../../application'; import { ActivityBarPosition } from '../activitybar/activityBar'; export function setup() { - describe.skip('Preferences', () => { + describe('Preferences', () => { it('turns off editor line numbers and verifies the live change', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/preferences/settings.ts b/test/smoke/src/areas/preferences/settings.ts index 6ac7bffece9..ecdef71c033 100644 --- a/test/smoke/src/areas/preferences/settings.ts +++ b/test/smoke/src/areas/preferences/settings.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import { Editor } from '../editor/editor'; import { Editors } from '../editor/editors'; import { Code } from '../../vscode/code'; +import { QuickOpen } from '../quickopen/quickopen'; export enum ActivityBarPosition { LEFT = 0, @@ -18,7 +19,7 @@ const SEARCH_INPUT = '.settings-search-input input'; export class SettingsEditor { - constructor(private code: Code, private userDataPath: string, private editors: Editors, private editor: Editor) { } + constructor(private code: Code, private userDataPath: string, private editors: Editors, private editor: Editor, private quickopen: QuickOpen) { } async addUserSetting(setting: string, value: string): Promise { await this.openSettings(); @@ -41,10 +42,6 @@ export class SettingsEditor { } private async openSettings(): Promise { - if (process.platform === 'darwin') { - await this.code.dispatchKeybinding('cmd+,'); - } else { - await this.code.dispatchKeybinding('ctrl+,'); - } + await this.quickopen.runCommand('Preferences: Open User Settings'); } } \ No newline at end of file diff --git a/test/smoke/src/areas/workbench/workbench.ts b/test/smoke/src/areas/workbench/workbench.ts index 661e0699a4d..005265f912c 100644 --- a/test/smoke/src/areas/workbench/workbench.ts +++ b/test/smoke/src/areas/workbench/workbench.ts @@ -55,7 +55,7 @@ export class Workbench { this.debug = new Debug(code, this.quickopen, this.editors, this.editor); this.statusbar = new StatusBar(code); this.problems = new Problems(code); - this.settingsEditor = new SettingsEditor(code, userDataPath, this.editors, this.editor); + this.settingsEditor = new SettingsEditor(code, userDataPath, this.editors, this.editor, this.quickopen); this.keybindingsEditor = new KeybindingsEditor(code); this.terminal = new Terminal(code); }