From b21435743d113c0d7db806341076781a1ed9e1b9 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Fri, 19 Jul 2019 19:21:54 -0700 Subject: [PATCH] Merge from vscode c873727e8bac95e7cbf5b154a9e6ae0986f2ce18 (#6446) --- build/yarn.lock | 6 +- .../resources/dark/refresh_inverse.svg | 5 +- .../azurecore/resources/light/refresh.svg | 5 +- .../configuration-editing/src/extension.ts | 19 +- .../json-language-features/server/README.md | 49 +- .../server/package.json | 2 +- .../theme-seti/build/update-icon-theme.js | 4 +- extensions/theme-seti/cgmanifest.json | 2 +- extensions/theme-seti/icons/seti.woff | Bin 33904 -> 34420 bytes .../theme-seti/icons/vs-seti-icon-theme.json | 666 ++++++++++------ package.json | 4 +- remote/package.json | 2 +- remote/yarn.lock | 8 +- src/sql/media/icons/start.svg | 4 +- src/sql/media/icons/start_inverse.svg | 4 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 8 +- src/vs/base/browser/ui/findinput/findInput.ts | 14 +- .../ui/findinput/findInputCheckboxes.ts | 10 +- src/vs/base/common/lifecycle.ts | 37 + .../common/config/commonEditorConfig.ts | 2 +- src/vs/editor/contrib/find/findModel.ts | 17 +- .../editor/contrib/find/findOptionsWidget.ts | 17 +- src/vs/editor/contrib/find/findWidget.css | 4 - src/vs/editor/contrib/find/findWidget.ts | 8 +- .../editor/contrib/find/simpleFindWidget.ts | 3 +- src/vs/editor/contrib/gotoError/gotoError.ts | 11 +- .../diagnostics/node/diagnosticsService.ts | 10 +- .../browser/remoteAuthorityResolverService.ts | 12 +- .../remote/common/remoteAgentConnection.ts | 1 + .../remote/common/remoteAuthorityResolver.ts | 13 +- .../remoteAuthorityResolverService.ts | 16 +- .../telemetry/common/telemetryService.ts | 6 +- src/vs/platform/theme/common/colorRegistry.ts | 3 +- src/vs/platform/theme/common/styler.ts | 8 +- src/vs/vscode.proposed.d.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 4 +- src/vs/workbench/api/common/extHostSCM.ts | 4 +- .../api/node/extHostExtensionService.ts | 17 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/common/commentModel.ts | 28 +- .../electron-browser/experimentService.ts | 22 +- .../extensions/browser/extensionsActions.ts | 163 ++-- .../browser/extensionsWorkbenchService.ts | 4 +- .../contrib/extensions/common/extensions.ts | 2 +- .../extensionsActions.test.ts | 742 +++++++++++++++++- .../extensionsWorkbenchService.test.ts | 8 +- .../contrib/files/common/explorerService.ts | 20 +- .../contrib/output/browser/outputActions.ts | 14 +- .../contrib/search/browser/searchView.ts | 4 + .../stats/electron-browser/workspaceStats.ts | 454 +---------- .../electron-browser/workspaceStatsService.ts | 477 +++++++++++ .../webview/browser/webviewEditorInput.ts | 12 +- .../webview/browser/webviewEditorService.ts | 6 +- .../common/variableResolver.ts | 7 + .../dialogs/browser/fileDialogService.ts | 4 +- .../common/extensionHostProcessManager.ts | 12 +- .../common/remoteExtensionHostClient.ts | 7 +- .../electron-browser/extensionService.ts | 10 +- .../test/browserKeyboardMapper.test.ts | 18 +- .../common/abstractRemoteAgentService.ts | 4 +- .../services/remote/node/tunnelService.ts | 4 +- src/vs/workbench/workbench.main.ts | 2 + yarn.lock | 8 +- 63 files changed, 2049 insertions(+), 1000 deletions(-) create mode 100644 src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts diff --git a/build/yarn.lock b/build/yarn.lock index b33452a884..bbb907bc75 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3614,9 +3614,9 @@ vsce@1.48.0: yazl "^2.2.2" vscode-ripgrep@^1.4.0: - version "1.5.4" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.4.tgz#dae1c3eef350513299341cdf96e622c00b548eff" - integrity sha512-Bs8SvFAkR0QHf09J46VgNo19yRikOtj/f0zHzK3AM3ICjCGNN/BNoG9of6zGVHUTO+6Mk1RbKglyOHLKr8D1lg== + version "1.5.5" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" + integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== vscode-telemetry-extractor@1.4.3: version "1.4.3" diff --git a/extensions/azurecore/resources/dark/refresh_inverse.svg b/extensions/azurecore/resources/dark/refresh_inverse.svg index d79fdaa4e8..ec0c43f0bc 100644 --- a/extensions/azurecore/resources/dark/refresh_inverse.svg +++ b/extensions/azurecore/resources/dark/refresh_inverse.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/extensions/azurecore/resources/light/refresh.svg b/extensions/azurecore/resources/light/refresh.svg index e034574819..a5b88123a0 100644 --- a/extensions/azurecore/resources/light/refresh.svg +++ b/extensions/azurecore/resources/light/refresh.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 0b69def737..0d051856c2 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -63,11 +63,20 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { const indexOf$ = document.lineAt(position.line).text.indexOf('$'); const startPosition = indexOf$ >= 0 ? new vscode.Position(position.line, indexOf$) : position; - return [{ label: 'workspaceFolder', detail: localize('workspaceFolder', "The path of the folder opened in VS Code") }, { label: 'workspaceFolderBasename', detail: localize('workspaceFolderBasename', "The name of the folder opened in VS Code without any slashes (/)") }, - { label: 'relativeFile', detail: localize('relativeFile', "The current opened file relative to ${workspaceFolder}") }, { label: 'file', detail: localize('file', "The current opened file") }, { label: 'cwd', detail: localize('cwd', "The task runner's current working directory on startup") }, - { label: 'lineNumber', detail: localize('lineNumber', "The current selected line number in the active file") }, { label: 'selectedText', detail: localize('selectedText', "The current selected text in the active file") }, - { label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") }, { label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") }, { label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") }, - { label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") }].map(variable => ({ + return [ + { label: 'workspaceFolder', detail: localize('workspaceFolder', "The path of the folder opened in VS Code") }, + { label: 'workspaceFolderBasename', detail: localize('workspaceFolderBasename', "The name of the folder opened in VS Code without any slashes (/)") }, + { label: 'relativeFile', detail: localize('relativeFile', "The current opened file relative to ${workspaceFolder}") }, + { label: 'relativeFileDirname', detail: localize('relativeFileDirname', "The current opened file's dirname relative to ${workspaceFolder}") }, + { label: 'file', detail: localize('file', "The current opened file") }, + { label: 'cwd', detail: localize('cwd', "The task runner's current working directory on startup") }, + { label: 'lineNumber', detail: localize('lineNumber', "The current selected line number in the active file") }, + { label: 'selectedText', detail: localize('selectedText', "The current selected text in the active file") }, + { label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") }, + { label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") }, + { label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") }, + { label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") } + ].map(variable => ({ label: '${' + variable.label + '}', range: new vscode.Range(startPosition, position), detail: variable.detail diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 9b02dc24ef..5ca997d9e1 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -21,6 +21,8 @@ The server implements the following capabilities of the language server protocol - [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to properties in the document. - [Document Colors](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentColor) for showing color decorators on values representing colors and [Color Presentation](https://microsoft.github.io/language-server-protocol/specification#textDocument_colorPresentation) for color presentation information to support color pickers. The location of colors is defined by the document's [JSON schema](http://json-schema.org/). All values marked with `"format": "color-hex"` (VSCode specific, non-standard JSON Schema extension) are considered color values. The supported color formats are `#rgb[a]` and `#rrggbb[aa]`. - [Code Formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting) supporting ranges and formatting the whole document. +- [Folding Ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) for all folding ranges in the document. +- Semantic Selection for semantic selection for one or multiple cursor positions. - [Diagnostics (Validation)](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics) are pushed for all open documents - syntax errors - structural validation based on the document's [JSON schema](http://json-schema.org/). @@ -90,10 +92,39 @@ To find the schema for a given JSON document, the server uses the following mech - The settings define a schema association based on the documents URL. Settings can either associate a schema URL to a file or path pattern, and they can directly provide a schema. - Additionally, schema associations can also be provided by a custom 'schemaAssociations' configuration call. -Schemas are identified by URLs. To load the content of a schema, the JSON language server tries to load from that URL or path. The following URL schemas are supported: +Schemas are identified by URLs. To load the content of a schema, the JSON language server either tries to load from that URI or path itself, or delegates to the client. + +The `initializationOptions.handledSchemaProtocols` initialization option defines which URLs are handled by the server. Requests for all other URIs are send to the client. + +`handledSchemaProtocols` is part of the initialization options and can't be changed while the server is running. + +```ts +let clientOptions: LanguageClientOptions = { + initializationOptions: { + handledSchemaProtocols: ['file'] // language server should only try to load file URLs + } + ... +} +``` + +If `handledSchemaProtocols` is not set, the JSON language server will load the following URLs itself: + - `http`, `https`: Loaded using NodeJS's HTTP support. Proxies can be configured through the settings. - `file`: Loaded using NodeJS's `fs` support. -- `vscode`: Loaded by an LSP call to the client. + +#### Schema content request + +Requests for schemas with URLs not handled by the server are forwarded to the client through an LSP request. This request is a JSON language server specific, non-standardized, extension to the LSP. + +Request: +- method: 'vscode/content' +- params: `string` - The schema URL to request. +- response: `string` - The content of the schema with the given URL + +#### Schema content change notification + +When the client is aware that a schema content has changed, it will notify the server through a notification. This notification is a JSON language server specific, non-standardized, extension to the LSP. +The server will, as a response, clear the schema content from the cache and reload the schema content when required again. #### Schema associations notification @@ -111,20 +142,6 @@ interface ISchemaAssociations { - keys: a file names or file path (separated by `/`). `*` can be used as a wildcard. - values: An array of schema URLs -#### Schema content request - -The schema content for schema URLs that start with `vscode://` will be requested from the client through an LSP request. This request is a JSON language server specific, non-standardized, extension to the LSP. - -Request: -- method: 'vscode/content' -- params: `string` - The schema URL to request. The server will only ask for URLs that start with `vscode://` -- response: `string` - The content of the schema with the given URL - -#### Schema content change notification - -When the client is aware that a schema content has changed, it will notify the server through a notification. This notification is a JSON language server specific, non-standardized, extension to the LSP. -The server will, as a response, clear the schema content from the cache and reload the schema content when required again. - Notification: - method: 'json/schemaContent' - params: `string` the URL of the schema that has changed. diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 68b639c2aa..c07dca1e44 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-json-languageserver", "description": "JSON language server", - "version": "1.0.1", + "version": "1.2.0", "author": "Microsoft Corporation", "license": "MIT", "engines": { diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js index 3686307f7d..69d945693c 100644 --- a/extensions/theme-seti/build/update-icon-theme.js +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -10,7 +10,7 @@ let fs = require('fs'); let https = require('https'); let url = require('url'); -// list of languagesIs not shipped with VSCode. The information is used to associate an icon with a langauge association +// list of languagesIs not shipped with VSCode. The information is used to associate an icon with a language association let nonBuiltInLanguages = { // { fileNames, extensions } "r": { extensions: ['r', 'rhistory', 'rprofile', 'rt'] }, "argdown": { extensions: ['ad', 'adown', 'argdown', 'argdn'] }, @@ -35,7 +35,7 @@ let nonBuiltInLanguages = { // { fileNames, extensions } "todo": { fileNames: ['todo'] } }; -let FROM_DISK = false; // set to true to take content from a repo checkedout next to the vscode repo +let FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo let font, fontMappingsFile, fileAssociationFile, colorsFile; if (!FROM_DISK) { diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 2b449830f5..c742c019ea 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "89175d7f9e0c70cd325b80a18a3c77fc8eb7c798" + "commitHash": "904c16acced1134a81b31d71d60293288c31334b" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index e590d77f6840c65388f761b878df92e329bb897e..b85727d01e463fc89c0bfaf2006eecb73d5d5244 100644 GIT binary patch delta 34142 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 delta 33620 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 diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 193c22fe61..d5a05b60ff 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -206,6 +206,14 @@ "fontCharacter": "\\E017", "fontColor": "#a074c4" }, + "_cpp_2_light": { + "fontCharacter": "\\E017", + "fontColor": "#b7b73b" + }, + "_cpp_2": { + "fontCharacter": "\\E017", + "fontColor": "#cbcb41" + }, "_crystal_light": { "fontCharacter": "\\E018", "fontColor": "#bfc2c1" @@ -246,999 +254,1103 @@ "fontCharacter": "\\E01C", "fontColor": "#cc3e44" }, - "_db_light": { + "_dart_light": { "fontCharacter": "\\E01D", + "fontColor": "#498ba7" + }, + "_dart": { + "fontCharacter": "\\E01D", + "fontColor": "#519aba" + }, + "_db_light": { + "fontCharacter": "\\E01E", "fontColor": "#dd4b78" }, "_db": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01E", "fontColor": "#f55385" }, "_default_light": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E01F", "fontColor": "#bfc2c1" }, "_default": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E01F", "fontColor": "#d4d7d6" }, "_docker_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#498ba7" }, "_docker": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#519aba" }, "_docker_1_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#455155" }, "_docker_1": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#4d5a5e" }, "_docker_2_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#7fae42" }, "_docker_2": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#8dc149" }, "_docker_3_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#dd4b78" }, "_docker_3": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#f55385" }, "_ejs_light": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#b7b73b" }, "_ejs": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#cbcb41" }, "_elixir_light": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E024", "fontColor": "#9068b0" }, "_elixir": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E024", "fontColor": "#a074c4" }, "_elixir_script_light": { - "fontCharacter": "\\E024", + "fontCharacter": "\\E025", "fontColor": "#9068b0" }, "_elixir_script": { - "fontCharacter": "\\E024", + "fontCharacter": "\\E025", "fontColor": "#a074c4" }, "_elm_light": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E026", "fontColor": "#498ba7" }, "_elm": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E026", "fontColor": "#519aba" }, "_eslint_light": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E028", "fontColor": "#9068b0" }, "_eslint": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E028", "fontColor": "#a074c4" }, "_eslint_1_light": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E028", "fontColor": "#455155" }, "_eslint_1": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E028", "fontColor": "#4d5a5e" }, "_ethereum_light": { - "fontCharacter": "\\E028", + "fontCharacter": "\\E029", "fontColor": "#498ba7" }, "_ethereum": { - "fontCharacter": "\\E028", + "fontCharacter": "\\E029", "fontColor": "#519aba" }, "_f-sharp_light": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02A", "fontColor": "#498ba7" }, "_f-sharp": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02A", "fontColor": "#519aba" }, "_favicon_light": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E02B", "fontColor": "#b7b73b" }, "_favicon": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E02B", "fontColor": "#cbcb41" }, "_firebase_light": { - "fontCharacter": "\\E02B", + "fontCharacter": "\\E02C", "fontColor": "#cc6d2e" }, "_firebase": { - "fontCharacter": "\\E02B", + "fontCharacter": "\\E02C", "fontColor": "#e37933" }, "_firefox_light": { - "fontCharacter": "\\E02C", + "fontCharacter": "\\E02D", "fontColor": "#cc6d2e" }, "_firefox": { - "fontCharacter": "\\E02C", + "fontCharacter": "\\E02D", "fontColor": "#e37933" }, "_font_light": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E02F", "fontColor": "#b8383d" }, "_font": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E02F", "fontColor": "#cc3e44" }, "_git_light": { - "fontCharacter": "\\E02F", + "fontCharacter": "\\E030", "fontColor": "#3b4b52" }, "_git": { - "fontCharacter": "\\E02F", + "fontCharacter": "\\E030", "fontColor": "#41535b" }, "_go_light": { - "fontCharacter": "\\E033", + "fontCharacter": "\\E034", "fontColor": "#498ba7" }, "_go": { - "fontCharacter": "\\E033", + "fontCharacter": "\\E034", "fontColor": "#519aba" }, "_go2_light": { - "fontCharacter": "\\E034", + "fontCharacter": "\\E035", "fontColor": "#498ba7" }, "_go2": { - "fontCharacter": "\\E034", + "fontCharacter": "\\E035", "fontColor": "#519aba" }, "_gradle_light": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E036", "fontColor": "#7fae42" }, "_gradle": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E036", "fontColor": "#8dc149" }, "_grails_light": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E037", "fontColor": "#7fae42" }, "_grails": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E037", "fontColor": "#8dc149" }, + "_graphql_light": { + "fontCharacter": "\\E038", + "fontColor": "#dd4b78" + }, + "_graphql": { + "fontCharacter": "\\E038", + "fontColor": "#f55385" + }, "_grunt_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E039", "fontColor": "#cc6d2e" }, "_grunt": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E039", "fontColor": "#e37933" }, "_gulp_light": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E03A", "fontColor": "#b8383d" }, "_gulp": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E03A", "fontColor": "#cc3e44" }, "_haml_light": { - "fontCharacter": "\\E03A", + "fontCharacter": "\\E03C", "fontColor": "#b8383d" }, "_haml": { - "fontCharacter": "\\E03A", + "fontCharacter": "\\E03C", "fontColor": "#cc3e44" }, + "_happenings_light": { + "fontCharacter": "\\E03D", + "fontColor": "#498ba7" + }, + "_happenings": { + "fontCharacter": "\\E03D", + "fontColor": "#519aba" + }, "_haskell_light": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E03E", "fontColor": "#9068b0" }, "_haskell": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E03E", "fontColor": "#a074c4" }, "_haxe_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#cc6d2e" }, "_haxe": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#e37933" }, "_haxe_1_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#b7b73b" }, "_haxe_1": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#cbcb41" }, "_haxe_2_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#498ba7" }, "_haxe_2": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#519aba" }, "_haxe_3_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#9068b0" }, "_haxe_3": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03F", "fontColor": "#a074c4" }, "_heroku_light": { - "fontCharacter": "\\E03D", + "fontCharacter": "\\E040", "fontColor": "#9068b0" }, "_heroku": { - "fontCharacter": "\\E03D", + "fontCharacter": "\\E040", "fontColor": "#a074c4" }, "_hex_light": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E041", "fontColor": "#b8383d" }, "_hex": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E041", "fontColor": "#cc3e44" }, "_html_light": { - "fontCharacter": "\\E03F", - "fontColor": "#cc6d2e" + "fontCharacter": "\\E042", + "fontColor": "#498ba7" }, "_html": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E042", + "fontColor": "#519aba" + }, + "_html_1_light": { + "fontCharacter": "\\E042", + "fontColor": "#7fae42" + }, + "_html_1": { + "fontCharacter": "\\E042", + "fontColor": "#8dc149" + }, + "_html_2_light": { + "fontCharacter": "\\E042", + "fontColor": "#b7b73b" + }, + "_html_2": { + "fontCharacter": "\\E042", + "fontColor": "#cbcb41" + }, + "_html_3_light": { + "fontCharacter": "\\E042", + "fontColor": "#cc6d2e" + }, + "_html_3": { + "fontCharacter": "\\E042", "fontColor": "#e37933" }, "_html_erb_light": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E043", "fontColor": "#b8383d" }, "_html_erb": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E043", "fontColor": "#cc3e44" }, "_ignored_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E044", "fontColor": "#3b4b52" }, "_ignored": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E044", "fontColor": "#41535b" }, "_illustrator_light": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E045", "fontColor": "#b7b73b" }, "_illustrator": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E045", "fontColor": "#cbcb41" }, "_image_light": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E046", "fontColor": "#9068b0" }, "_image": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E046", "fontColor": "#a074c4" }, "_info_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E047", "fontColor": "#498ba7" }, "_info": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E047", "fontColor": "#519aba" }, "_ionic_light": { - "fontCharacter": "\\E045", + "fontCharacter": "\\E048", "fontColor": "#498ba7" }, "_ionic": { - "fontCharacter": "\\E045", + "fontCharacter": "\\E048", "fontColor": "#519aba" }, "_jade_light": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E049", "fontColor": "#b8383d" }, "_jade": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E049", "fontColor": "#cc3e44" }, "_java_light": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E04A", "fontColor": "#b8383d" }, "_java": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E04A", "fontColor": "#cc3e44" }, "_javascript_light": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04B", "fontColor": "#b7b73b" }, "_javascript": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04B", "fontColor": "#cbcb41" }, "_javascript_1_light": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04B", "fontColor": "#cc6d2e" }, "_javascript_1": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04B", "fontColor": "#e37933" }, "_javascript_2_light": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04B", "fontColor": "#498ba7" }, "_javascript_2": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04B", "fontColor": "#519aba" }, "_jenkins_light": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E04C", "fontColor": "#b8383d" }, "_jenkins": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E04C", "fontColor": "#cc3e44" }, "_jinja_light": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E04D", "fontColor": "#b8383d" }, "_jinja": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E04D", "fontColor": "#cc3e44" }, "_json_light": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04F", "fontColor": "#b7b73b" }, "_json": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04F", "fontColor": "#cbcb41" }, "_json_1_light": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04F", "fontColor": "#7fae42" }, "_json_1": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04F", "fontColor": "#8dc149" }, "_julia_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E050", "fontColor": "#9068b0" }, "_julia": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E050", "fontColor": "#a074c4" }, "_karma_light": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E051", "fontColor": "#7fae42" }, "_karma": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E051", "fontColor": "#8dc149" }, "_kotlin_light": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E052", "fontColor": "#cc6d2e" }, "_kotlin": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E052", "fontColor": "#e37933" }, "_less_light": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E053", "fontColor": "#498ba7" }, "_less": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E053", "fontColor": "#519aba" }, "_license_light": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E054", "fontColor": "#b7b73b" }, "_license": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E054", "fontColor": "#cbcb41" }, "_license_1_light": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E054", "fontColor": "#cc6d2e" }, "_license_1": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E054", "fontColor": "#e37933" }, "_license_2_light": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E054", "fontColor": "#b8383d" }, "_license_2": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E054", "fontColor": "#cc3e44" }, "_liquid_light": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E055", "fontColor": "#7fae42" }, "_liquid": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E055", "fontColor": "#8dc149" }, "_livescript_light": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E056", "fontColor": "#498ba7" }, "_livescript": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E056", "fontColor": "#519aba" }, "_lock_light": { - "fontCharacter": "\\E054", + "fontCharacter": "\\E057", "fontColor": "#7fae42" }, "_lock": { - "fontCharacter": "\\E054", + "fontCharacter": "\\E057", "fontColor": "#8dc149" }, "_lua_light": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E058", "fontColor": "#498ba7" }, "_lua": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E058", "fontColor": "#519aba" }, "_makefile_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#cc6d2e" }, "_makefile": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#e37933" }, "_makefile_1_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#9068b0" }, "_makefile_1": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#a074c4" }, "_makefile_2_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#627379" }, "_makefile_2": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#6d8086" }, "_makefile_3_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#498ba7" }, "_makefile_3": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E059", "fontColor": "#519aba" }, "_markdown_light": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E05A", "fontColor": "#498ba7" }, "_markdown": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E05A", "fontColor": "#519aba" }, "_maven_light": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E05B", "fontColor": "#b8383d" }, "_maven": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E05B", "fontColor": "#cc3e44" }, "_mdo_light": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E05C", "fontColor": "#b8383d" }, "_mdo": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E05C", "fontColor": "#cc3e44" }, "_mustache_light": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E05D", "fontColor": "#cc6d2e" }, "_mustache": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E05D", "fontColor": "#e37933" }, "_npm_light": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05F", "fontColor": "#3b4b52" }, "_npm": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05F", "fontColor": "#41535b" }, "_npm_1_light": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05F", "fontColor": "#b8383d" }, "_npm_1": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05F", "fontColor": "#cc3e44" }, "_npm_ignored_light": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E060", "fontColor": "#3b4b52" }, "_npm_ignored": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E060", "fontColor": "#41535b" }, "_nunjucks_light": { - "fontCharacter": "\\E05E", + "fontCharacter": "\\E061", "fontColor": "#7fae42" }, "_nunjucks": { - "fontCharacter": "\\E05E", + "fontCharacter": "\\E061", "fontColor": "#8dc149" }, "_ocaml_light": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E062", "fontColor": "#cc6d2e" }, "_ocaml": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E062", "fontColor": "#e37933" }, "_odata_light": { - "fontCharacter": "\\E060", + "fontCharacter": "\\E063", "fontColor": "#cc6d2e" }, "_odata": { - "fontCharacter": "\\E060", + "fontCharacter": "\\E063", "fontColor": "#e37933" }, + "_pddl_light": { + "fontCharacter": "\\E064", + "fontColor": "#9068b0" + }, + "_pddl": { + "fontCharacter": "\\E064", + "fontColor": "#a074c4" + }, "_pdf_light": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E065", "fontColor": "#b8383d" }, "_pdf": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E065", "fontColor": "#cc3e44" }, "_perl_light": { - "fontCharacter": "\\E062", + "fontCharacter": "\\E066", "fontColor": "#498ba7" }, "_perl": { - "fontCharacter": "\\E062", + "fontCharacter": "\\E066", "fontColor": "#519aba" }, "_photoshop_light": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E067", "fontColor": "#498ba7" }, "_photoshop": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E067", "fontColor": "#519aba" }, "_php_light": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E068", "fontColor": "#9068b0" }, "_php": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E068", "fontColor": "#a074c4" }, + "_plan_light": { + "fontCharacter": "\\E069", + "fontColor": "#7fae42" + }, + "_plan": { + "fontCharacter": "\\E069", + "fontColor": "#8dc149" + }, + "_platformio_light": { + "fontCharacter": "\\E06A", + "fontColor": "#cc6d2e" + }, + "_platformio": { + "fontCharacter": "\\E06A", + "fontColor": "#e37933" + }, "_powershell_light": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E06B", "fontColor": "#498ba7" }, "_powershell": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E06B", "fontColor": "#519aba" }, "_pug_light": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E06D", "fontColor": "#b8383d" }, "_pug": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E06D", "fontColor": "#cc3e44" }, "_puppet_light": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E06E", "fontColor": "#b7b73b" }, "_puppet": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E06E", "fontColor": "#cbcb41" }, "_python_light": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E06F", "fontColor": "#498ba7" }, "_python": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E06F", "fontColor": "#519aba" }, "_react_light": { - "fontCharacter": "\\E06B", + "fontCharacter": "\\E071", "fontColor": "#498ba7" }, "_react": { - "fontCharacter": "\\E06B", + "fontCharacter": "\\E071", "fontColor": "#519aba" }, + "_react_1_light": { + "fontCharacter": "\\E071", + "fontColor": "#cc6d2e" + }, + "_react_1": { + "fontCharacter": "\\E071", + "fontColor": "#e37933" + }, + "_react_2_light": { + "fontCharacter": "\\E071", + "fontColor": "#b7b73b" + }, + "_react_2": { + "fontCharacter": "\\E071", + "fontColor": "#cbcb41" + }, + "_reasonml_light": { + "fontCharacter": "\\E072", + "fontColor": "#b8383d" + }, + "_reasonml": { + "fontCharacter": "\\E072", + "fontColor": "#cc3e44" + }, "_rollup_light": { - "fontCharacter": "\\E06C", + "fontCharacter": "\\E073", "fontColor": "#b8383d" }, "_rollup": { - "fontCharacter": "\\E06C", + "fontCharacter": "\\E073", "fontColor": "#cc3e44" }, "_ruby_light": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E074", "fontColor": "#b8383d" }, "_ruby": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E074", "fontColor": "#cc3e44" }, "_rust_light": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E075", "fontColor": "#627379" }, "_rust": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E075", "fontColor": "#6d8086" }, "_salesforce_light": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E076", "fontColor": "#498ba7" }, "_salesforce": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E076", "fontColor": "#519aba" }, "_sass_light": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E077", "fontColor": "#dd4b78" }, "_sass": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E077", "fontColor": "#f55385" }, "_sbt_light": { - "fontCharacter": "\\E071", + "fontCharacter": "\\E078", "fontColor": "#498ba7" }, "_sbt": { - "fontCharacter": "\\E071", + "fontCharacter": "\\E078", "fontColor": "#519aba" }, "_scala_light": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E079", "fontColor": "#b8383d" }, "_scala": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E079", "fontColor": "#cc3e44" }, "_shell_light": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E07C", "fontColor": "#455155" }, "_shell": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E07C", "fontColor": "#4d5a5e" }, "_slim_light": { - "fontCharacter": "\\E076", + "fontCharacter": "\\E07D", "fontColor": "#cc6d2e" }, "_slim": { - "fontCharacter": "\\E076", + "fontCharacter": "\\E07D", "fontColor": "#e37933" }, "_smarty_light": { - "fontCharacter": "\\E077", + "fontCharacter": "\\E07E", "fontColor": "#b7b73b" }, "_smarty": { - "fontCharacter": "\\E077", + "fontCharacter": "\\E07E", "fontColor": "#cbcb41" }, "_spring_light": { - "fontCharacter": "\\E078", + "fontCharacter": "\\E07F", "fontColor": "#7fae42" }, "_spring": { - "fontCharacter": "\\E078", + "fontCharacter": "\\E07F", "fontColor": "#8dc149" }, "_stylelint_light": { - "fontCharacter": "\\E079", + "fontCharacter": "\\E080", "fontColor": "#bfc2c1" }, "_stylelint": { - "fontCharacter": "\\E079", + "fontCharacter": "\\E080", "fontColor": "#d4d7d6" }, "_stylelint_1_light": { - "fontCharacter": "\\E079", + "fontCharacter": "\\E080", "fontColor": "#455155" }, "_stylelint_1": { - "fontCharacter": "\\E079", + "fontCharacter": "\\E080", "fontColor": "#4d5a5e" }, "_stylus_light": { - "fontCharacter": "\\E07A", + "fontCharacter": "\\E081", "fontColor": "#7fae42" }, "_stylus": { - "fontCharacter": "\\E07A", + "fontCharacter": "\\E081", "fontColor": "#8dc149" }, "_sublime_light": { - "fontCharacter": "\\E07B", + "fontCharacter": "\\E082", "fontColor": "#cc6d2e" }, "_sublime": { - "fontCharacter": "\\E07B", + "fontCharacter": "\\E082", "fontColor": "#e37933" }, "_svg_light": { - "fontCharacter": "\\E07C", + "fontCharacter": "\\E083", "fontColor": "#9068b0" }, "_svg": { - "fontCharacter": "\\E07C", + "fontCharacter": "\\E083", "fontColor": "#a074c4" }, "_svg_1_light": { - "fontCharacter": "\\E07C", + "fontCharacter": "\\E083", "fontColor": "#498ba7" }, "_svg_1": { - "fontCharacter": "\\E07C", + "fontCharacter": "\\E083", "fontColor": "#519aba" }, "_swift_light": { - "fontCharacter": "\\E07D", + "fontCharacter": "\\E084", "fontColor": "#cc6d2e" }, "_swift": { - "fontCharacter": "\\E07D", + "fontCharacter": "\\E084", "fontColor": "#e37933" }, "_terraform_light": { - "fontCharacter": "\\E07E", + "fontCharacter": "\\E085", "fontColor": "#9068b0" }, "_terraform": { - "fontCharacter": "\\E07E", + "fontCharacter": "\\E085", "fontColor": "#a074c4" }, "_tex_light": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#498ba7" }, "_tex": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#519aba" }, "_tex_1_light": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#b7b73b" }, "_tex_1": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#cbcb41" }, "_tex_2_light": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#cc6d2e" }, "_tex_2": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#e37933" }, "_tex_3_light": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#bfc2c1" }, "_tex_3": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E086", "fontColor": "#d4d7d6" }, "_todo": { - "fontCharacter": "\\E081" + "fontCharacter": "\\E088" + }, + "_tsconfig_light": { + "fontCharacter": "\\E089", + "fontColor": "#498ba7" + }, + "_tsconfig": { + "fontCharacter": "\\E089", + "fontColor": "#519aba" }, "_twig_light": { - "fontCharacter": "\\E082", + "fontCharacter": "\\E08A", "fontColor": "#7fae42" }, "_twig": { - "fontCharacter": "\\E082", + "fontCharacter": "\\E08A", "fontColor": "#8dc149" }, "_typescript_light": { - "fontCharacter": "\\E083", + "fontCharacter": "\\E08B", "fontColor": "#498ba7" }, "_typescript": { - "fontCharacter": "\\E083", + "fontCharacter": "\\E08B", "fontColor": "#519aba" }, "_typescript_1_light": { - "fontCharacter": "\\E083", + "fontCharacter": "\\E08B", "fontColor": "#b7b73b" }, "_typescript_1": { - "fontCharacter": "\\E083", + "fontCharacter": "\\E08B", "fontColor": "#cbcb41" }, "_vala_light": { - "fontCharacter": "\\E084", + "fontCharacter": "\\E08C", "fontColor": "#627379" }, "_vala": { - "fontCharacter": "\\E084", + "fontCharacter": "\\E08C", "fontColor": "#6d8086" }, "_video_light": { - "fontCharacter": "\\E085", + "fontCharacter": "\\E08D", "fontColor": "#dd4b78" }, "_video": { - "fontCharacter": "\\E085", + "fontCharacter": "\\E08D", "fontColor": "#f55385" }, "_vue_light": { - "fontCharacter": "\\E086", + "fontCharacter": "\\E08E", "fontColor": "#7fae42" }, "_vue": { - "fontCharacter": "\\E086", + "fontCharacter": "\\E08E", "fontColor": "#8dc149" }, "_wasm_light": { - "fontCharacter": "\\E087", + "fontCharacter": "\\E08F", "fontColor": "#9068b0" }, "_wasm": { - "fontCharacter": "\\E087", + "fontCharacter": "\\E08F", "fontColor": "#a074c4" }, "_wat_light": { - "fontCharacter": "\\E088", + "fontCharacter": "\\E090", "fontColor": "#9068b0" }, "_wat": { - "fontCharacter": "\\E088", + "fontCharacter": "\\E090", "fontColor": "#a074c4" }, "_webpack_light": { - "fontCharacter": "\\E089", + "fontCharacter": "\\E091", "fontColor": "#498ba7" }, "_webpack": { - "fontCharacter": "\\E089", + "fontCharacter": "\\E091", "fontColor": "#519aba" }, "_wgt_light": { - "fontCharacter": "\\E08A", + "fontCharacter": "\\E092", "fontColor": "#498ba7" }, "_wgt": { - "fontCharacter": "\\E08A", + "fontCharacter": "\\E092", "fontColor": "#519aba" }, "_windows_light": { - "fontCharacter": "\\E08B", + "fontCharacter": "\\E093", "fontColor": "#498ba7" }, "_windows": { - "fontCharacter": "\\E08B", + "fontCharacter": "\\E093", "fontColor": "#519aba" }, "_word_light": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E094", "fontColor": "#498ba7" }, "_word": { - "fontCharacter": "\\E08C", + "fontCharacter": "\\E094", "fontColor": "#519aba" }, "_xls_light": { - "fontCharacter": "\\E08D", + "fontCharacter": "\\E095", "fontColor": "#7fae42" }, "_xls": { - "fontCharacter": "\\E08D", + "fontCharacter": "\\E095", "fontColor": "#8dc149" }, "_xml_light": { - "fontCharacter": "\\E08E", + "fontCharacter": "\\E096", "fontColor": "#cc6d2e" }, "_xml": { - "fontCharacter": "\\E08E", + "fontCharacter": "\\E096", "fontColor": "#e37933" }, "_yarn_light": { - "fontCharacter": "\\E08F", + "fontCharacter": "\\E097", "fontColor": "#498ba7" }, "_yarn": { - "fontCharacter": "\\E08F", + "fontCharacter": "\\E097", "fontColor": "#519aba" }, "_yml_light": { - "fontCharacter": "\\E090", + "fontCharacter": "\\E098", "fontColor": "#9068b0" }, "_yml": { - "fontCharacter": "\\E090", + "fontCharacter": "\\E098", "fontColor": "#a074c4" }, "_zip_light": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E099", "fontColor": "#b8383d" }, "_zip": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E099", "fontColor": "#cc3e44" }, "_zip_1_light": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E099", "fontColor": "#627379" }, "_zip_1": { - "fontCharacter": "\\E091", + "fontCharacter": "\\E099", "fontColor": "#6d8086" }, // {{SQL CARBON EDIT}} @@ -1262,6 +1374,10 @@ "asm": "_asm", "s": "_asm", "h": "_c_1", + "aspx": "_html", + "ascx": "_html_1", + "asax": "_html_2", + "master": "_html_2", "hh": "_cpp_1", "hpp": "_cpp_1", "hxx": "_cpp_1", @@ -1299,6 +1415,8 @@ "article": "_go", "gradle": "_gradle", "gsp": "_grails", + "gql": "_graphql", + "graphql": "_graphql", "haml": "_haml", "hs": "_haskell", "lhs": "_haskell", @@ -1310,6 +1428,7 @@ "classpath": "_java", "js.map": "_javascript", "spec.js": "_javascript_1", + "test.js": "_javascript_1", "es": "_javascript", "es5": "_javascript", "es7": "_javascript", @@ -1318,6 +1437,7 @@ "jl": "_julia", "kt": "_kotlin", "kts": "_kotlin", + "dart": "_dart", "liquid": "_liquid", "ls": "_livescript", "argdown": "_argdown", @@ -1339,10 +1459,18 @@ "cmxa": "_ocaml", "odata": "_odata", "php.inc": "_php", + "pddl": "_pddl", + "plan": "_plan", + "happenings": "_happenings", "pug": "_pug", "pp": "_puppet", "epp": "_puppet", + "spec.jsx": "_react_1", + "test.jsx": "_react_1", "cjsx": "_react", + "spec.tsx": "_react_2", + "test.tsx": "_react_2", + "re": "_reasonml", "r": "_R", "erb": "_html_erb", "erb.html": "_html_erb", @@ -1365,6 +1493,7 @@ "toml": "_config", "twig": "_twig", "spec.ts": "_typescript_1", + "test.ts": "_typescript_1", "vala": "_vala", "vapi": "_vala", "vue": "_vue", @@ -1468,6 +1597,7 @@ "gulpfile": "_gulp", "ionic.config.json": "_ionic", "ionic.project": "_ionic", + "platformio.ini": "_platformio", "rollup.config.js": "_rollup", "sass-lint.yml": "_sass", "stylelint.config.js": "_stylelint", @@ -1475,6 +1605,9 @@ "yarn.lock": "_yarn", "webpack.config.js": "_webpack", "webpack.config.build.js": "_webpack", + "webpack.common.js": "_webpack", + "webpack.dev.js": "_webpack", + "webpack.prod.js": "_webpack", "license": "_license", "licence": "_license", "copying": "_license", @@ -1494,6 +1627,7 @@ "bat": "_windows", "clojure": "_clojure", "coffeescript": "_coffee", + "jsonc": "_tsconfig", "c": "_c", "cpp": "_cpp", "csharp": "_c-sharp", @@ -1503,7 +1637,7 @@ "go": "_go2", "groovy": "_grails", "handlebars": "_mustache", - "html": "_html", + "html": "_html_3", "properties": "_java", "java": "_java", "javascriptreact": "_react", @@ -1514,12 +1648,14 @@ "makefile": "_makefile", "markdown": "_markdown", "objective-c": "_c_2", + "objective-cpp": "_cpp_2", "perl": "_perl", "php": "_php", "powershell": "_powershell", "jade": "_jade", "python": "_python", "r": "_R", + "razor": "_html", "ruby": "_ruby", "rust": "_rust", "scss": "_sass", @@ -1561,6 +1697,10 @@ "asm": "_asm_light", "s": "_asm_light", "h": "_c_1_light", + "aspx": "_html_light", + "ascx": "_html_1_light", + "asax": "_html_2_light", + "master": "_html_2_light", "hh": "_cpp_1_light", "hpp": "_cpp_1_light", "hxx": "_cpp_1_light", @@ -1598,6 +1738,8 @@ "article": "_go_light", "gradle": "_gradle_light", "gsp": "_grails_light", + "gql": "_graphql_light", + "graphql": "_graphql_light", "haml": "_haml_light", "hs": "_haskell_light", "lhs": "_haskell_light", @@ -1609,6 +1751,7 @@ "classpath": "_java_light", "js.map": "_javascript_light", "spec.js": "_javascript_1_light", + "test.js": "_javascript_1_light", "es": "_javascript_light", "es5": "_javascript_light", "es7": "_javascript_light", @@ -1617,6 +1760,7 @@ "jl": "_julia_light", "kt": "_kotlin_light", "kts": "_kotlin_light", + "dart": "_dart_light", "liquid": "_liquid_light", "ls": "_livescript_light", "argdown": "_argdown_light", @@ -1638,10 +1782,18 @@ "cmxa": "_ocaml_light", "odata": "_odata_light", "php.inc": "_php_light", + "pddl": "_pddl_light", + "plan": "_plan_light", + "happenings": "_happenings_light", "pug": "_pug_light", "pp": "_puppet_light", "epp": "_puppet_light", + "spec.jsx": "_react_1_light", + "test.jsx": "_react_1_light", "cjsx": "_react_light", + "spec.tsx": "_react_2_light", + "test.tsx": "_react_2_light", + "re": "_reasonml_light", "r": "_R_light", "erb": "_html_erb_light", "erb.html": "_html_erb_light", @@ -1664,6 +1816,7 @@ "toml": "_config_light", "twig": "_twig_light", "spec.ts": "_typescript_1_light", + "test.ts": "_typescript_1_light", "vala": "_vala_light", "vapi": "_vala_light", "vue": "_vue_light", @@ -1743,6 +1896,7 @@ "bat": "_windows_light", "clojure": "_clojure_light", "coffeescript": "_coffee_light", + "jsonc": "_tsconfig_light", "c": "_c_light", "cpp": "_cpp_light", "csharp": "_c-sharp_light", @@ -1752,7 +1906,7 @@ "go": "_go2_light", "groovy": "_grails_light", "handlebars": "_mustache_light", - "html": "_html_light", + "html": "_html_3_light", "properties": "_java_light", "java": "_java_light", "javascriptreact": "_react_light", @@ -1763,12 +1917,14 @@ "makefile": "_makefile_light", "markdown": "_markdown_light", "objective-c": "_c_2_light", + "objective-cpp": "_cpp_2_light", "perl": "_perl_light", "php": "_php_light", "powershell": "_powershell_light", "jade": "_jade_light", "python": "_python_light", "r": "_R_light", + "razor": "_html_light", "ruby": "_ruby_light", "rust": "_rust_light", "scss": "_sass_light", @@ -1829,6 +1985,7 @@ "gulpfile": "_gulp_light", "ionic.config.json": "_ionic_light", "ionic.project": "_ionic_light", + "platformio.ini": "_platformio_light", "rollup.config.js": "_rollup_light", "sass-lint.yml": "_sass_light", "stylelint.config.js": "_stylelint_light", @@ -1836,6 +1993,9 @@ "yarn.lock": "_yarn_light", "webpack.config.js": "_webpack_light", "webpack.config.build.js": "_webpack_light", + "webpack.common.js": "_webpack_light", + "webpack.dev.js": "_webpack_light", + "webpack.prod.js": "_webpack_light", "license": "_license_light", "licence": "_license_light", "copying": "_license_light", @@ -1851,5 +2011,5 @@ "Schema Compare": "scmp" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/89175d7f9e0c70cd325b80a18a3c77fc8eb7c798" + "version": "https://github.com/jesseweed/seti-ui/commit/904c16acced1134a81b31d71d60293288c31334b" } \ No newline at end of file diff --git a/package.json b/package.json index 57acd2a2eb..1ff677c50e 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "2.1.7", "vscode-proxy-agent": "0.4.0", - "vscode-ripgrep": "^1.5.4", + "vscode-ripgrep": "^1.5.5", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", "xterm": "3.15.0-beta71", @@ -192,4 +192,4 @@ "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" } -} \ No newline at end of file +} diff --git a/remote/package.json b/remote/package.json index a4113e1e74..081eb5c607 100644 --- a/remote/package.json +++ b/remote/package.json @@ -18,7 +18,7 @@ "spdlog": "^0.9.0", "vscode-chokidar": "2.1.7", "vscode-proxy-agent": "0.4.0", - "vscode-ripgrep": "^1.5.4", + "vscode-ripgrep": "^1.5.5", "vscode-textmate": "^4.2.2", "xterm": "3.15.0-beta71", "xterm-addon-search": "0.2.0-beta2", diff --git a/remote/yarn.lock b/remote/yarn.lock index 9aa7137893..3ed8f02de3 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -1123,10 +1123,10 @@ vscode-proxy-agent@0.4.0: https-proxy-agent "2.2.1" socks-proxy-agent "4.0.1" -vscode-ripgrep@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.4.tgz#dae1c3eef350513299341cdf96e622c00b548eff" - integrity sha512-Bs8SvFAkR0QHf09J46VgNo19yRikOtj/f0zHzK3AM3ICjCGNN/BNoG9of6zGVHUTO+6Mk1RbKglyOHLKr8D1lg== +vscode-ripgrep@^1.5.5: + version "1.5.5" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" + integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== vscode-textmate@^4.2.2: version "4.2.2" diff --git a/src/sql/media/icons/start.svg b/src/sql/media/icons/start.svg index 9ef467f2c0..8b0a58eca9 100644 --- a/src/sql/media/icons/start.svg +++ b/src/sql/media/icons/start.svg @@ -1 +1,3 @@ -continue \ No newline at end of file + + + diff --git a/src/sql/media/icons/start_inverse.svg b/src/sql/media/icons/start_inverse.svg index 3be0c24f6f..2563bfa114 100644 --- a/src/sql/media/icons/start_inverse.svg +++ b/src/sql/media/icons/start_inverse.svg @@ -1 +1,3 @@ -continue \ No newline at end of file + + + diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index e8c1b2b17f..9e196784b7 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -22,10 +22,12 @@ export interface ICheckboxOpts extends ICheckboxStyles { export interface ICheckboxStyles { inputActiveOptionBorder?: Color; + inputActiveOptionBackground?: Color; } const defaultOpts = { - inputActiveOptionBorder: Color.fromHex('#007ACC') + inputActiveOptionBorder: Color.fromHex('#007ACC00'), + inputActiveOptionBackground: Color.fromHex('#0E639C50') }; export class CheckboxActionViewItem extends BaseActionViewItem { @@ -149,12 +151,16 @@ export class Checkbox extends Widget { if (styles.inputActiveOptionBorder) { this._opts.inputActiveOptionBorder = styles.inputActiveOptionBorder; } + if (styles.inputActiveOptionBackground) { + this._opts.inputActiveOptionBackground = styles.inputActiveOptionBackground; + } this.applyStyles(); } protected applyStyles(): void { if (this.domNode) { this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : 'transparent'; + this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : 'transparent'; } } diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 7af439b18e..d582f39d06 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -33,6 +33,7 @@ export interface IFindInputOptions extends IFindInputStyles { export interface IFindInputStyles extends IInputBoxStyles { inputActiveOptionBorder?: Color; + inputActiveOptionBackground?: Color; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -48,6 +49,7 @@ export class FindInput extends Widget { private fixFocusOnOptionClickEnabled = true; private inputActiveOptionBorder?: Color; + private inputActiveOptionBackground?: Color; private inputBackground?: Color; private inputForeground?: Color; private inputBorder?: Color; @@ -97,6 +99,7 @@ export class FindInput extends Widget { this.label = options.label || NLS_DEFAULT_LABEL; this.inputActiveOptionBorder = options.inputActiveOptionBorder; + this.inputActiveOptionBackground = options.inputActiveOptionBackground; this.inputBackground = options.inputBackground; this.inputForeground = options.inputForeground; this.inputBorder = options.inputBorder; @@ -173,6 +176,7 @@ export class FindInput extends Widget { public style(styles: IFindInputStyles): void { this.inputActiveOptionBorder = styles.inputActiveOptionBorder; + this.inputActiveOptionBackground = styles.inputActiveOptionBackground; this.inputBackground = styles.inputBackground; this.inputForeground = styles.inputForeground; this.inputBorder = styles.inputBorder; @@ -194,6 +198,7 @@ export class FindInput extends Widget { if (this.domNode) { const checkBoxStyles: ICheckboxStyles = { inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground, }; this.regex.style(checkBoxStyles); this.wholeWords.style(checkBoxStyles); @@ -294,7 +299,8 @@ export class FindInput extends Widget { this.regex = this._register(new RegexCheckbox({ appendTitle: appendRegexLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground })); this._register(this.regex.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -310,7 +316,8 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsCheckbox({ appendTitle: appendWholeWordsLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground })); this._register(this.wholeWords.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -323,7 +330,8 @@ export class FindInput extends Widget { this.caseSensitive = this._register(new CaseSensitiveCheckbox({ appendTitle: appendCaseSensitiveLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground })); this._register(this.caseSensitive.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); diff --git a/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts b/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts index b4b7fed0b6..46dd9d67aa 100644 --- a/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts +++ b/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts @@ -12,6 +12,7 @@ export interface IFindInputCheckboxOpts { readonly appendTitle: string; readonly isChecked: boolean; readonly inputActiveOptionBorder?: Color; + readonly inputActiveOptionBackground?: Color; } const NLS_CASE_SENSITIVE_CHECKBOX_LABEL = nls.localize('caseDescription', "Match Case"); @@ -24,7 +25,8 @@ export class CaseSensitiveCheckbox extends Checkbox { actionClassName: 'monaco-case-sensitive', title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + opts.appendTitle, isChecked: opts.isChecked, - inputActiveOptionBorder: opts.inputActiveOptionBorder + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionBackground: opts.inputActiveOptionBackground }); } } @@ -35,7 +37,8 @@ export class WholeWordsCheckbox extends Checkbox { actionClassName: 'monaco-whole-word', title: NLS_WHOLE_WORD_CHECKBOX_LABEL + opts.appendTitle, isChecked: opts.isChecked, - inputActiveOptionBorder: opts.inputActiveOptionBorder + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionBackground: opts.inputActiveOptionBackground }); } } @@ -46,7 +49,8 @@ export class RegexCheckbox extends Checkbox { actionClassName: 'monaco-regex', title: NLS_REGEX_CHECKBOX_LABEL + opts.appendTitle, isChecked: opts.isChecked, - inputActiveOptionBorder: opts.inputActiveOptionBorder + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionBackground: opts.inputActiveOptionBackground }); } } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index abd65d46b4..ca8b49a1f9 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -207,6 +207,43 @@ export class MutableDisposable implements IDisposable { } } +/** + * Wrapper class that stores a disposable that is not currently "owned" by anyone. + * + * Example use cases: + * + * - Express that a function/method will take ownership of a disposable parameter. + * - Express that a function returns a disposable that the caller must explicitly take ownership of. + */ +export class UnownedDisposable extends Disposable { + private _hasBeenAcquired = false; + private _value?: T; + + public constructor(value: T) { + super(); + this._value = value; + } + + public acquire(): T { + if (this._hasBeenAcquired) { + throw new Error('This disposable has already been acquired'); + } + this._hasBeenAcquired = true; + const value = this._value!; + this._value = undefined; + return value; + } + + public dispose() { + super.dispose(); + if (!this._hasBeenAcquired) { + this._hasBeenAcquired = true; + this._value!.dispose(); + this._value = undefined; + } + } +} + export interface IReference extends IDisposable { readonly object: T; } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index e055e81897..e15d8b414a 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -460,7 +460,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.fastScrollSensitivity': { 'type': 'number', 'default': EDITOR_DEFAULTS.viewInfo.scrollbar.fastScrollSensitivity, - 'markdownDescription': nls.localize('fastScrollSensitivity', "Scrolling speed mulitiplier when pressing `Alt`.") + 'markdownDescription': nls.localize('fastScrollSensitivity', "Scrolling speed multiplier when pressing `Alt`.") }, 'editor.multiCursorModifier': { 'type': 'string', diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index 17505c4f34..70846107cc 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -5,7 +5,7 @@ import { RunOnceScheduler, TimeoutTimer } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ReplaceCommand, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -71,7 +71,7 @@ export class FindModelBoundToEditorModel { private readonly _editor: IActiveCodeEditor; private readonly _state: FindReplaceState; - private _toDispose: IDisposable[]; + private readonly _toDispose = new DisposableStore(); private readonly _decorations: FindDecorations; private _ignoreModelContentChanged: boolean; private readonly _startSearchingTimer: TimeoutTimer; @@ -82,17 +82,16 @@ export class FindModelBoundToEditorModel { constructor(editor: IActiveCodeEditor, state: FindReplaceState) { this._editor = editor; this._state = state; - this._toDispose = []; this._isDisposed = false; this._startSearchingTimer = new TimeoutTimer(); this._decorations = new FindDecorations(editor); - this._toDispose.push(this._decorations); + this._toDispose.add(this._decorations); this._updateDecorationsScheduler = new RunOnceScheduler(() => this.research(false), 100); - this._toDispose.push(this._updateDecorationsScheduler); + this._toDispose.add(this._updateDecorationsScheduler); - this._toDispose.push(this._editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { + this._toDispose.add(this._editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { if ( e.reason === CursorChangeReason.Explicit || e.reason === CursorChangeReason.Undo @@ -103,7 +102,7 @@ export class FindModelBoundToEditorModel { })); this._ignoreModelContentChanged = false; - this._toDispose.push(this._editor.onDidChangeModelContent((e) => { + this._toDispose.add(this._editor.onDidChangeModelContent((e) => { if (this._ignoreModelContentChanged) { return; } @@ -115,7 +114,7 @@ export class FindModelBoundToEditorModel { this._updateDecorationsScheduler.schedule(); })); - this._toDispose.push(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e))); + this._toDispose.add(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e))); this.research(false, this._state.searchScope); } @@ -123,7 +122,7 @@ export class FindModelBoundToEditorModel { public dispose(): void { this._isDisposed = true; dispose(this._startSearchingTimer); - this._toDispose = dispose(this._toDispose); + this._toDispose.dispose(); } private _onStateChanged(e: FindReplaceStateChangedEvent): void { diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index 68594813e4..eeb61ffa09 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPosit import { FIND_IDS } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -47,11 +47,13 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('aria-hidden', 'true'); const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder); + const inputActiveOptionBackgroundColor = themeService.getTheme().getColor(inputActiveOptionBackground); this.caseSensitive = this._register(new CaseSensitiveCheckbox({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), isChecked: this._state.matchCase, - inputActiveOptionBorder: inputActiveOptionBorderColor + inputActiveOptionBorder: inputActiveOptionBorderColor, + inputActiveOptionBackground: inputActiveOptionBackgroundColor })); this._domNode.appendChild(this.caseSensitive.domNode); this._register(this.caseSensitive.onChange(() => { @@ -63,7 +65,8 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.wholeWords = this._register(new WholeWordsCheckbox({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), isChecked: this._state.wholeWord, - inputActiveOptionBorder: inputActiveOptionBorderColor + inputActiveOptionBorder: inputActiveOptionBorderColor, + inputActiveOptionBackground: inputActiveOptionBackgroundColor })); this._domNode.appendChild(this.wholeWords.domNode); this._register(this.wholeWords.onChange(() => { @@ -75,7 +78,8 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.regex = this._register(new RegexCheckbox({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), isChecked: this._state.isRegex, - inputActiveOptionBorder: inputActiveOptionBorderColor + inputActiveOptionBorder: inputActiveOptionBorderColor, + inputActiveOptionBackground: inputActiveOptionBackgroundColor })); this._domNode.appendChild(this.regex.domNode); this._register(this.regex.onChange(() => { @@ -179,7 +183,10 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { } private _applyTheme(theme: ITheme) { - let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) }; + let inputStyles = { + inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), + inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground) + }; this.caseSensitive.style(inputStyles); this.wholeWords.style(inputStyles); this.regex.style(inputStyles); diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 5644623d0c..d1aa36c7ff 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -289,10 +289,6 @@ background-color: rgba(255, 255, 255, 0.1); } -.monaco-editor.vs-dark .find-widget .monaco-checkbox .checkbox:checked + .label { - background-color: rgba(255, 255, 255, 0.1); -} - .monaco-editor.hc-black .find-widget .close-fw, .monaco-editor.vs-dark .find-widget .close-fw { background-image: url('images/close-dark.svg'); diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index f0d8174565..279d107d9b 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -27,7 +27,7 @@ import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_REPLACE_INPUT_FOCUSED, FIND_IDS, MA import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -560,6 +560,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _applyTheme(theme: ITheme) { let inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), + inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), inputBackground: theme.getColor(inputBackground), inputForeground: theme.getColor(inputForeground), inputBorder: theme.getColor(inputBorder), @@ -1217,4 +1218,9 @@ registerThemingParticipant((theme, collector) => { if (inputActiveBorder) { collector.addRule(`.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .label { border: 1px solid ${inputActiveBorder.toString()}; }`); } + + const inputActiveBackground = theme.getColor(inputActiveOptionBackground); + if (inputActiveBackground) { + collector.addRule(`.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .label { background-color: ${inputActiveBackground.toString()}; }`); + } }); diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index 954cdf81b2..6807fe2424 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -15,7 +15,7 @@ import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBo import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { editorWidgetBackground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; @@ -180,6 +180,7 @@ export abstract class SimpleFindWidget extends Widget { public updateTheme(theme: ITheme): void { const inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), + inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), inputBackground: theme.getColor(inputBackground), inputForeground: theme.getColor(inputForeground), inputBorder: theme.getColor(inputBorder), diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index ee221e2732..68eb858450 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -32,7 +32,7 @@ class MarkerModel { private readonly _editor: ICodeEditor; private _markers: IMarker[]; private _nextIdx: number; - private _toUnbind: IDisposable[]; + private readonly _toUnbind = new DisposableStore(); private _ignoreSelectionChange: boolean; private readonly _onCurrentMarkerChanged: Emitter; private readonly _onMarkerSetChanged: Emitter; @@ -41,15 +41,14 @@ class MarkerModel { this._editor = editor; this._markers = []; this._nextIdx = -1; - this._toUnbind = []; this._ignoreSelectionChange = false; this._onCurrentMarkerChanged = new Emitter(); this._onMarkerSetChanged = new Emitter(); this.setMarkers(markers); // listen on editor - this._toUnbind.push(this._editor.onDidDispose(() => this.dispose())); - this._toUnbind.push(this._editor.onDidChangeCursorPosition(() => { + this._toUnbind.add(this._editor.onDidDispose(() => this.dispose())); + this._toUnbind.add(this._editor.onDidChangeCursorPosition(() => { if (this._ignoreSelectionChange) { return; } @@ -190,7 +189,7 @@ class MarkerModel { } public dispose(): void { - this._toUnbind = dispose(this._toUnbind); + this._toUnbind.dispose(); } } diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 917d81b4c2..ecd08b4a35 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -520,10 +520,14 @@ export class DiagnosticsService implements IDiagnosticsService { if (folderUri.scheme === 'file') { const folder = folderUri.fsPath; collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => { + type WorkspaceStatItemClassification = { + name: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + }; type WorkspaceStatsClassification = { - fileTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - configTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - launchConfigs: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + fileTypes: WorkspaceStatItemClassification; + configTypes: WorkspaceStatItemClassification; + launchConfigs: WorkspaceStatItemClassification; }; type WorkspaceStatsEvent = { fileTypes: WorkspaceStatItem[]; diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 02f066378e..5138f4a42d 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResolvedAuthority, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverService { @@ -12,12 +12,16 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS constructor() { } - resolveAuthority(authority: string): Promise { + resolveAuthority(authority: string): Promise { if (authority.indexOf(':') >= 0) { const pieces = authority.split(':'); - return Promise.resolve({ authority, host: pieces[0], port: parseInt(pieces[1], 10) }); + return Promise.resolve({ + authority: { authority, host: pieces[0], port: parseInt(pieces[1], 10) } + }); } - return Promise.resolve({ authority, host: authority, port: 80 }); + return Promise.resolve({ + authority: { authority, host: authority, port: 80 } + }); } clearResolvedAuthority(authority: string): void { diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index dd59ae9e7c..1e98f9510c 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -163,6 +163,7 @@ export interface IRemoteExtensionHostStartParams { debugId?: string; break?: boolean; port?: number | null; + env?: { [key: string]: string | null }; } interface IExtensionHostConnectionResult { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 34c0934ac1..80fc027183 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -13,6 +13,15 @@ export interface ResolvedAuthority { readonly port: number; } +export interface ResolvedOptions { + readonly extensionHostEnv?: { [key: string]: string | null }; +} + +export interface ResolverResult { + authority: ResolvedAuthority; + options?: ResolvedOptions; +} + export enum RemoteAuthorityResolverErrorCode { Unknown = 'Unknown', NotAvailable = 'NotAvailable', @@ -61,9 +70,9 @@ export interface IRemoteAuthorityResolverService { _serviceBrand: any; - resolveAuthority(authority: string): Promise; + resolveAuthority(authority: string): Promise; clearResolvedAuthority(authority: string): void; - setResolvedAuthority(resolvedAuthority: ResolvedAuthority): void; + setResolvedAuthority(resolvedAuthority: ResolvedAuthority, resolvedOptions?: ResolvedOptions): void; setResolvedAuthorityError(authority: string, err: any): void; } diff --git a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts index 131a211cb7..1f648271d8 100644 --- a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts @@ -3,15 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResolvedAuthority, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, IRemoteAuthorityResolverService, ResolverResult, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ipcRenderer as ipc } from 'electron'; import * as errors from 'vs/base/common/errors'; class PendingResolveAuthorityRequest { constructor( - public readonly resolve: (value: ResolvedAuthority) => void, + public readonly resolve: (value: ResolverResult) => void, public readonly reject: (err: any) => void, - public readonly promise: Promise, + public readonly promise: Promise, ) { } } @@ -26,11 +26,11 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS this._resolveAuthorityRequests = Object.create(null); } - resolveAuthority(authority: string): Promise { + resolveAuthority(authority: string): Promise { if (!this._resolveAuthorityRequests[authority]) { - let resolve: (value: ResolvedAuthority) => void; + let resolve: (value: ResolverResult) => void; let reject: (err: any) => void; - let promise = new Promise((_resolve, _reject) => { + let promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); @@ -46,11 +46,11 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS } } - setResolvedAuthority(resolvedAuthority: ResolvedAuthority) { + setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions) { if (this._resolveAuthorityRequests[resolvedAuthority.authority]) { let request = this._resolveAuthorityRequests[resolvedAuthority.authority]; ipc.send('vscode:remoteAuthorityResolved', resolvedAuthority); - request.resolve(resolvedAuthority); + request.resolve({ authority: resolvedAuthority, options }); } } diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 371b7cd376..d9493cfb1b 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -10,7 +10,7 @@ import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils' import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { cloneAndChange, mixin } from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; @@ -35,7 +35,7 @@ export class TelemetryService implements ITelemetryService { private _userOptIn: boolean; private _enabled: boolean; - private _disposables: IDisposable[] = []; + private readonly _disposables = new DisposableStore(); private _cleanupPatterns: RegExp[] = []; constructor( @@ -113,7 +113,7 @@ export class TelemetryService implements ITelemetryService { } dispose(): void { - this._disposables = dispose(this._disposables); + this._disposables.dispose(); } publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 5dec8a1d57..07b9fe0e86 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -205,7 +205,8 @@ export const widgetShadow = registerColor('widget.shadow', { dark: '#000000', li export const inputBackground = registerColor('input.background', { dark: '#3C3C3C', light: Color.white, hc: Color.black }, nls.localize('inputBoxBackground', "Input box background.")); export const inputForeground = registerColor('input.foreground', { dark: foreground, light: foreground, hc: foreground }, nls.localize('inputBoxForeground', "Input box foreground.")); export const inputBorder = registerColor('input.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('inputBoxBorder', "Input box border.")); -export const inputActiveOptionBorder = registerColor('inputOption.activeBorder', { dark: '#007ACC', light: '#007ACC', hc: activeContrastBorder }, nls.localize('inputBoxActiveOptionBorder', "Border color of activated options in input fields.")); +export const inputActiveOptionBorder = registerColor('inputOption.activeBorder', { dark: '#007ACC00', light: '#007ACC00', hc: contrastBorder }, nls.localize('inputBoxActiveOptionBorder', "Border color of activated options in input fields.")); +export const inputActiveOptionBackground = registerColor('inputOption.activeBackground', { dark: transparent(focusBorder, 0.5), light: transparent(focusBorder, 0.3), hc: null }, nls.localize('inputOption.activeBackground', "Background color of activated options in input fields.")); export const inputPlaceholderForeground = registerColor('input.placeholderForeground', { light: transparent(foreground, 0.5), dark: transparent(foreground, 0.5), hc: transparent(foreground, 0.7) }, nls.localize('inputPlaceholderForeground', "Input box foreground color for placeholder text.")); export const inputValidationInfoBackground = registerColor('inputValidation.infoBackground', { dark: '#063B49', light: '#D6ECF2', hc: Color.black }, nls.localize('inputValidationInfoBackground', "Input validation background color for information severity.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 1c37120f8c..36db6d057f 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -59,11 +59,13 @@ export function attachStyler(themeService: IThemeServic export interface ICheckboxStyleOverrides extends IStyleOverrides { inputActiveOptionBorderColor?: ColorIdentifier; + inputActiveOptionBackgroundColor?: ColorIdentifier; } export function attachCheckboxStyler(widget: IThemable, themeService: IThemeService, style?: ICheckboxStyleOverrides): IDisposable { return attachStyler(themeService, { - inputActiveOptionBorder: (style && style.inputActiveOptionBorderColor) || inputActiveOptionBorder + inputActiveOptionBorder: (style && style.inputActiveOptionBorderColor) || inputActiveOptionBorder, + inputActiveOptionBackground: (style && style.inputActiveOptionBackgroundColor) || inputActiveOptionBackground } as ICheckboxStyleOverrides, widget); } @@ -85,6 +87,7 @@ export interface IInputBoxStyleOverrides extends IStyleOverrides { inputForeground?: ColorIdentifier; inputBorder?: ColorIdentifier; inputActiveOptionBorder?: ColorIdentifier; + inputActiveOptionBackground?: ColorIdentifier; inputValidationInfoBorder?: ColorIdentifier; inputValidationInfoBackground?: ColorIdentifier; inputValidationInfoForeground?: ColorIdentifier; @@ -146,6 +149,7 @@ export function attachFindInputBoxStyler(widget: IThemable, themeService: ITheme inputForeground: (style && style.inputForeground) || inputForeground, inputBorder: (style && style.inputBorder) || inputBorder, inputActiveOptionBorder: (style && style.inputActiveOptionBorder) || inputActiveOptionBorder, + inputActiveOptionBackground: (style && style.inputActiveOptionBackground) || inputActiveOptionBackground, inputValidationInfoBorder: (style && style.inputValidationInfoBorder) || inputValidationInfoBorder, inputValidationInfoBackground: (style && style.inputValidationInfoBackground) || inputValidationInfoBackground, inputValidationInfoForeground: (style && style.inputValidationInfoForeground) || inputValidationInfoForeground, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4f66d1891a..b39f4b1c89 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -94,6 +94,12 @@ declare module 'vscode' { constructor(host: string, port: number); } + export interface ResolvedOptions { + extensionHostEnv?: { [key: string]: string | null }; + } + + export type ResolverResult = ResolvedAuthority & ResolvedOptions; + export class RemoteAuthorityResolverError extends Error { static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError; static TemporarilyNotAvailable(message?: string): RemoteAuthorityResolverError; @@ -102,7 +108,7 @@ declare module 'vscode' { } export interface RemoteAuthorityResolver { - resolve(authority: string, context: RemoteAuthorityResolverContext): ResolvedAuthority | Thenable; + resolve(authority: string, context: RemoteAuthorityResolverContext): ResolverResult | Thenable; } export interface ResourceLabelFormatter { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4762748dc4..fd158d3ea2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -31,7 +31,7 @@ import { LogLevel } from 'vs/platform/log/common/log'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import * as quickInput from 'vs/platform/quickinput/common/quickInput'; -import { RemoteAuthorityResolverErrorCode, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAuthorityResolverErrorCode, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import * as statusbar from 'vs/platform/statusbar/common/statusbar'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; @@ -859,7 +859,7 @@ export interface IResolveAuthorityErrorResult { export interface IResolveAuthorityOKResult { type: 'ok'; - value: ResolvedAuthority; + value: ResolverResult; } export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAuthorityOKResult; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index d1813f1295..86451374b4 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -6,7 +6,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { debounce } from 'vs/base/common/decorators'; -import { dispose, IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, CommandDto } from './extHost.protocol'; @@ -263,7 +263,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG } readonly handle = ExtHostSourceControlResourceGroup._handlePool++; - private _disposables: IDisposable[] = []; constructor( private _proxy: MainThreadSCMShape, @@ -353,7 +352,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG dispose(): void { this._proxy.$unregisterGroup(this._sourceControlHandle, this.handle); - this._disposables = dispose(this._disposables); this._onDidDispose.fire(); } } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index d8c28fc3d9..5b2fe81ba1 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -36,6 +36,7 @@ import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; import { IURITransformer } from 'vs/base/common/uriIpc'; +import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; interface ITestRunner { /** Old test runner API, as exported from `vscode/lib/testrunner` */ @@ -680,12 +681,22 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { try { const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); + + // Split merged API result into separate authority/options + const authority: ResolvedAuthority = { + authority: remoteAuthority, + host: result.host, + port: result.port + }; + const options: ResolvedOptions = { + extensionHostEnv: result.extensionHostEnv + }; + return { type: 'ok', value: { - authority: remoteAuthority, - host: result.host, - port: result.port, + authority, + options } }; } catch (err) { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 1945492d4a..ff8abe09a6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -20,10 +20,10 @@ export class CommentsDataSource implements IDataSource { return 'root'; } if (element instanceof ResourceWithCommentThreads) { - return element.id; + return `${element.owner}-${element.id}`; } if (element instanceof CommentNode) { - return `${element.resource.toString()}-${element.comment.uniqueIdInThread}`; + return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}`; } return ''; } diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index 2716606952..2b6ecd70b5 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -15,13 +15,15 @@ export interface ICommentThreadChangedEvent extends CommentThreadChangedEvent { } export class CommentNode { + owner: string; threadId: string; range: IRange; comment: Comment; replies: CommentNode[] = []; resource: URI; - constructor(threadId: string, resource: URI, comment: Comment, range: IRange) { + constructor(owner: string, threadId: string, resource: URI, comment: Comment, range: IRange) { + this.owner = owner; this.threadId = threadId; this.comment = comment; this.resource = resource; @@ -35,18 +37,20 @@ export class CommentNode { export class ResourceWithCommentThreads { id: string; + owner: string; commentThreads: CommentNode[]; // The top level comments on the file. Replys are nested under each node. resource: URI; - constructor(resource: URI, commentThreads: CommentThread[]) { + constructor(owner: string, resource: URI, commentThreads: CommentThread[]) { + this.owner = owner; this.id = resource.toString(); this.resource = resource; - this.commentThreads = commentThreads.filter(thread => thread.comments && thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(resource, thread)); + this.commentThreads = commentThreads.filter(thread => thread.comments && thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(owner, resource, thread)); } - public static createCommentNode(resource: URI, commentThread: CommentThread): CommentNode { + public static createCommentNode(owner: string, resource: URI, commentThread: CommentThread): CommentNode { const { threadId, comments, range } = commentThread; - const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(threadId!, resource, comment, range)); + const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId!, resource, comment, range)); if (commentNodes.length > 1) { commentNodes[0].replies = commentNodes.slice(1, commentNodes.length); } @@ -65,7 +69,7 @@ export class CommentsModel { } public setCommentThreads(owner: string, commentThreads: CommentThread[]): void { - this.commentThreadsMap.set(owner, this.groupByResource(commentThreads)); + this.commentThreadsMap.set(owner, this.groupByResource(owner, commentThreads)); this.resourceCommentThreads = flatten(values(this.commentThreadsMap)); } @@ -97,9 +101,9 @@ export class CommentsModel { // Find comment node on resource that is that thread and replace it const index = firstIndex(matchingResourceData.commentThreads, (commentThread) => commentThread.threadId === thread.threadId); if (index >= 0) { - matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread); + matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread); } else if (thread.comments && thread.comments.length) { - matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread)); + matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread)); } }); @@ -108,10 +112,10 @@ export class CommentsModel { if (existingResource.length) { const resource = existingResource[0]; if (thread.comments && thread.comments.length) { - resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(resource.resource, thread)); + resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, resource.resource, thread)); } } else { - threadsForOwner.push(new ResourceWithCommentThreads(URI.parse(thread.resource!), [thread])); + threadsForOwner.push(new ResourceWithCommentThreads(owner, URI.parse(thread.resource!), [thread])); } }); @@ -133,11 +137,11 @@ export class CommentsModel { } } - private groupByResource(commentThreads: CommentThread[]): ResourceWithCommentThreads[] { + private groupByResource(owner: string, commentThreads: CommentThread[]): ResourceWithCommentThreads[] { const resourceCommentThreads: ResourceWithCommentThreads[] = []; const commentThreadsByResource = new Map(); for (const group of groupBy(commentThreads, CommentsModel._compareURIs)) { - commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(URI.parse(group[0].resource!), group)); + commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(owner, URI.parse(group[0].resource!), group)); } commentThreadsByResource.forEach((v, i, m) => { diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts index 3bc4966f70..a24a9f35fd 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts @@ -10,17 +10,17 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { language } from 'vs/base/common/platform'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { Emitter, Event } from 'vs/base/common/event'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { WorkspaceStats } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ExperimentState, IExperimentAction, IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IProductService } from 'vs/platform/product/common/product'; +import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; interface IExperimentStorageState { enabled: boolean; @@ -60,7 +60,6 @@ export class ExperimentService extends Disposable implements IExperimentService private _experiments: IExperiment[] = []; private _loadExperimentsPromise: Promise; private _curatedMapping = Object.create(null); - private _disposables: IDisposable[] = []; private readonly _onExperimentEnabled = this._register(new Emitter()); onExperimentEnabled: Event = this._onExperimentEnabled.event; @@ -74,7 +73,8 @@ export class ExperimentService extends Disposable implements IExperimentService @ILifecycleService private readonly lifecycleService: ILifecycleService, @IRequestService private readonly requestService: IRequestService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService ) { super(); @@ -355,7 +355,7 @@ export class ExperimentService extends Disposable implements IExperimentService onSaveHandler.dispose(); return; } - e.forEach(event => { + e.forEach(async event => { if (event.kind !== StateChange.SAVED || latestExperimentState.state !== ExperimentState.Evaluating || date === latestExperimentState.lastEditedDate @@ -370,10 +370,12 @@ export class ExperimentService extends Disposable implements IExperimentService filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath); } if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) { - workspaceCheck = !!WorkspaceStats.TAGS && fileEdits.workspaceIncludes.some(x => !!WorkspaceStats.TAGS[x]); + const tags = await this.workspaceStatsService.getTags(); + workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]); } if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) { - workspaceCheck = !!WorkspaceStats.TAGS && !fileEdits.workspaceExcludes.some(x => !!WorkspaceStats.TAGS[x]); + const tags = await this.workspaceStatsService.getTags(); + workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]); } if (filePathCheck && workspaceCheck) { latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1; @@ -389,14 +391,10 @@ export class ExperimentService extends Disposable implements IExperimentService } } }); - this._disposables.push(onSaveHandler); + this._register(onSaveHandler); return ExperimentState.Evaluating; }); } - - dispose() { - this._disposables = dispose(this._disposables); - } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index ee1807b12d..6f8a93ff97 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -17,7 +17,7 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -176,19 +176,22 @@ export class InstallAction extends ExtensionAction { } update(): void { - if (!this.extension || this.extension.type === ExtensionType.System || this.extension.state === ExtensionState.Installed) { - this.enabled = false; - this.class = InstallAction.Class; - this.label = InstallAction.INSTALL_LABEL; - return; - } this.enabled = false; - if (this.extensionsWorkbenchService.canInstall(this.extension)) { - const local = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; - this.enabled = !local || (!!local.local && isLanguagePackExtension(local.local.manifest)); + this.class = InstallAction.Class; + this.label = InstallAction.INSTALL_LABEL; + if (this.extension && this.extension.type === ExtensionType.User) { + if (this.extension.state === ExtensionState.Uninstalled && this.extensionsWorkbenchService.canInstall(this.extension)) { + this.enabled = true; + this.updateLabel(); + return; + } + if (this.extension.state === ExtensionState.Installing) { + this.enabled = false; + this.updateLabel(); + this.class = this.extension.state === ExtensionState.Installing ? InstallAction.InstallingClass : InstallAction.Class; + return; + } } - this.class = this.extension.state === ExtensionState.Installing ? InstallAction.InstallingClass : InstallAction.Class; - this.updateLabel(); } private updateLabel(): void { @@ -283,60 +286,55 @@ export class InstallAction extends ExtensionAction { } } -export class RemoteInstallAction extends ExtensionAction { +export class InstallInOtherServerAction extends ExtensionAction { - private static INSTALL_LABEL = localize('install', "Install"); - private static INSTALLING_LABEL = localize('installing', "Installing"); + protected static INSTALL_LABEL = localize('install', "Install"); + protected static INSTALLING_LABEL = localize('installing', "Installing"); private static readonly Class = 'extension-action prominent install'; private static readonly InstallingClass = 'extension-action install installing'; updateWhenCounterExtensionChanges: boolean = true; - private installing: boolean = false; + protected installing: boolean = false; constructor( + id: string, + private readonly server: IExtensionManagementServer | null, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @ILabelService private readonly labelService: ILabelService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, ) { - super(`extensions.remoteinstall`, RemoteInstallAction.INSTALL_LABEL, RemoteInstallAction.Class, false); - this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); + super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false); this.updateLabel(); this.update(); } private updateLabel(): void { - if (this.installing) { - this.label = RemoteInstallAction.INSTALLING_LABEL; - this.tooltip = this.label; - return; - } - const remoteServer = this.extensionManagementServerService.remoteExtensionManagementServer; - if (remoteServer) { - this.label = `${RemoteInstallAction.INSTALL_LABEL} on ${remoteServer.label}`; - this.tooltip = this.label; - return; - } + this.label = this.getLabel(); + this.tooltip = this.label; + } + + protected getLabel(): string { + return this.installing ? InstallInOtherServerAction.INSTALLING_LABEL : + this.server ? `${InstallInOtherServerAction.INSTALL_LABEL} on ${this.server.label}` + : InstallInOtherServerAction.INSTALL_LABEL; + } update(): void { this.enabled = false; - this.class = RemoteInstallAction.Class; + this.class = InstallInOtherServerAction.Class; if (this.installing) { this.enabled = true; - this.class = RemoteInstallAction.InstallingClass; + this.class = InstallInOtherServerAction.InstallingClass; this.updateLabel(); return; } - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer - // Installed User Extension - && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed - // Local Workspace Extension - && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) - // Extension does not exist in remote - && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer) + + if ( + this.extension && this.extension.local && this.server && this.extension.state === ExtensionState.Installed + // disabled by extension kind or it is a language pack extension + && (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest)) + // Not installed in other server and can install in other server + && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server) && this.extensionsWorkbenchService.canInstall(this.extension) ) { this.enabled = true; @@ -346,13 +344,13 @@ export class RemoteInstallAction extends ExtensionAction { } async run(): Promise { - if (this.extensionManagementServerService.remoteExtensionManagementServer && !this.installing) { + if (this.server && !this.installing) { this.installing = true; this.update(); this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); if (this.extension.gallery) { - await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(this.extension.gallery); + await this.server.extensionManagementService.installFromGallery(this.extension.gallery); this.installing = false; this.update(); } @@ -360,77 +358,30 @@ export class RemoteInstallAction extends ExtensionAction { } } -export class LocalInstallAction extends ExtensionAction { - - private static INSTALL_LABEL = localize('install locally', "Install Locally"); - private static INSTALLING_LABEL = localize('installing', "Installing"); - - private static readonly Class = 'extension-action prominent install'; - private static readonly InstallingClass = 'extension-action install installing'; - - updateWhenCounterExtensionChanges: boolean = true; - private installing: boolean = false; +export class RemoteInstallAction extends InstallInOtherServerAction { constructor( - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @ILabelService private readonly labelService: ILabelService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService ) { - super(`extensions.localinstall`, LocalInstallAction.INSTALL_LABEL, LocalInstallAction.Class, false); - this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); - this.updateLabel(); - this.update(); + super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, extensionsWorkbenchService); } - private updateLabel(): void { - if (this.installing) { - this.label = LocalInstallAction.INSTALLING_LABEL; - this.tooltip = this.label; - return; - } - this.label = `${LocalInstallAction.INSTALL_LABEL}`; - this.tooltip = this.label; +} + +export class LocalInstallAction extends InstallInOtherServerAction { + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService + ) { + super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, extensionsWorkbenchService); } - update(): void { - this.enabled = false; - this.class = LocalInstallAction.Class; - if (this.installing) { - this.enabled = true; - this.class = LocalInstallAction.InstallingClass; - this.updateLabel(); - return; - } - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer - // Installed User Extension - && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed - // Remote UI or Language pack Extension - && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) - // Extension does not exist in local - && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer) - && this.extensionsWorkbenchService.canInstall(this.extension) - ) { - this.enabled = true; - this.updateLabel(); - return; - } + protected getLabel(): string { + return this.installing ? InstallInOtherServerAction.INSTALLING_LABEL : localize('install locally', "Install Locally"); } - async run(): Promise { - if (this.extensionManagementServerService.localExtensionManagementServer && !this.installing) { - this.installing = true; - this.update(); - this.extensionsWorkbenchService.open(this.extension); - alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); - if (this.extension.gallery) { - await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(this.extension.gallery); - this.installing = false; - this.update(); - } - } - } } export class UninstallAction extends ExtensionAction { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index c0915495e1..ee002a91eb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -64,8 +64,8 @@ class Extension implements IExtension { @IProductService private readonly productService: IProductService ) { } - get type(): ExtensionType | undefined { - return this.local ? this.local.type : undefined; + get type(): ExtensionType { + return this.local ? this.local.type : ExtensionType.User; } get name(): string { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index f55e9ec640..35a6987037 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -34,7 +34,7 @@ export const enum ExtensionState { } export interface IExtension { - readonly type?: ExtensionType; + readonly type: ExtensionType; readonly state: ExtensionState; readonly name: string; readonly displayName: string; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 891fbb0883..5291d1e70e 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -21,7 +21,7 @@ import { TestExtensionEnablementService } from 'vs/workbench/services/extensionM import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -42,6 +42,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ILabelService } from 'vs/platform/label/common/label'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import { IProductService } from 'vs/platform/product/common/product'; +import { Schemas } from 'vs/base/common/network'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; suite('ExtensionsActions Test', () => { @@ -53,7 +56,7 @@ suite('ExtensionsActions Test', () => { didUninstallEvent: Emitter; - suiteSetup(() => { + setup(async () => { installEvent = new Emitter(); didInstallEvent = new Emitter(); uninstallEvent = new Emitter(); @@ -91,9 +94,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); instantiationService.stub(IURLService, URLService); - }); - setup(async () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); @@ -1357,6 +1358,682 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); + test('Test remote install action is enabled for local workspace extension', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + }); + + test('Test remote install action when installing local workspace extension', async (done) => { + // multi server setup + const remoteExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + remoteExtensionManagementService.installFromGallery = () => new Promise(c => c(aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }))); + const disposable = testObject.onDidChange(() => { + if (testObject.label === 'Installing' && testObject.enabled) { + disposable.dispose(); + done(); + } + }); + testObject.run(); + }); + + test('Test remote install action when installing local workspace extension is finished', async (done) => { + // multi server setup + const remoteExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); + const onDidInstallEvent = new Emitter(); + remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + const installedExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + remoteExtensionManagementService.installFromGallery = () => new Promise(c => c(installedExtension)); + await testObject.run(); + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + + const disposable = testObject.onDidChange(() => { + if (testObject.label === 'Install on remote' && !testObject.enabled) { + disposable.dispose(); + done(); + } + }); + onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); + }); + + test('Test remote install action is enabled for disabled local workspace extension', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + await instantiationService.get(IExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + }); + + test('Test remote install action is disabled when extension is not set', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for extension which is not installed', async () => { + // multi server setup + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const pager = await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = pager.firstPage[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for local workspace extension which is disabled in env', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled when remote server is not available', async () => { + // single server setup + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for local workspace extension if it is uninstalled locally', async () => { + // multi server setup + const extensionManagementService = instantiationService.get(IExtensionManagementService); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + + uninstallEvent.fire(localWorkspaceExtension.identifier); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for local workspace extension if it is installed in remote', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for local workspace extension if it cannot be installed', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for local ui extension if it is not installed in remote', async () => { + // multi server setup + const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is disabled for local ui extension if it is also installed in remote', async () => { + // multi server setup + const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test remote install action is enabled for locally installed language pack extension', async () => { + // multi server setup + const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([languagePackExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + }); + + test('Test remote install action is disabled if local language pack extension is uninstalled', async () => { + // multi server setup + const extensionManagementService = instantiationService.get(IExtensionManagementService); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`) }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [languagePackExtension]); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install on remote', testObject.label); + + uninstallEvent.fire(languagePackExtension.identifier); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is enabled for remote ui extension', async () => { + // multi server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + }); + + test('Test local install action when installing remote ui extension', async (done) => { + // multi server setup + const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + localExtensionManagementService.installFromGallery = () => new Promise(c => c(aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }))); + const disposable = testObject.onDidChange(() => { + if (testObject.label === 'Installing' && testObject.enabled) { + disposable.dispose(); + done(); + } + }); + testObject.run(); + }); + + test('Test local install action when installing remote ui extension is finished', async (done) => { + // multi server setup + const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + const installedExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + localExtensionManagementService.installFromGallery = () => new Promise(c => c(installedExtension)); + await testObject.run(); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + + const disposable = testObject.onDidChange(() => { + if (testObject.label === 'Install Locally' && !testObject.enabled) { + disposable.dispose(); + done(); + } + }); + onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); + }); + + test('Test local install action is enabled for disabled remote ui extension', async () => { + // multi server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + await instantiationService.get(IExtensionEnablementService).setEnablement([remoteUIExtension], EnablementState.DisabledGlobally); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + }); + + test('Test local install action is disabled when extension is not set', async () => { + // multi server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for extension which is not installed', async () => { + // multi server setup + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const pager = await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = pager.firstPage[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for remote ui extension which is disabled in env', async () => { + // multi server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled when local server is not available', async () => { + // single server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aSingleRemoteExtensionManagementServerService(instantiationService, createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for remote ui extension if it is installed in local', async () => { + // multi server setup + const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for remoteUI extension if it is uninstalled locally', async () => { + // multi server setup + const extensionManagementService = instantiationService.get(IExtensionManagementService); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [remoteUIExtension]); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + + uninstallEvent.fire(remoteUIExtension.identifier); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for remote UI extension if it cannot be installed', async () => { + // multi server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for remote workspace extension if it is not installed in local', async () => { + // multi server setup + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is disabled for remote workspace extension if it is also installed in local', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspae' }, { location: URI.file(`pub.a`) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test local install action is enabled for remotely installed language pack extension', async () => { + // multi server setup + const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([languagePackExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + }); + + test('Test local install action is disabled if remote language pack extension is uninstalled', async () => { + // multi server setup + const extensionManagementService = instantiationService.get(IExtensionManagementService); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [languagePackExtension]); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + + uninstallEvent.fire(languagePackExtension.identifier); + assert.ok(!testObject.enabled); + }); test(`RecommendToFolderAction`, () => { // TODO: Implement test @@ -1386,4 +2063,61 @@ suite('ExtensionsActions Test', () => { return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! }; } + function aSingleRemoteExtensionManagementServerService(instantiationService: TestInstantiationService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { + const remoteExtensionManagementServer: IExtensionManagementServer = { + authority: 'vscode-remote', + label: 'remote', + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + }; + return { + _serviceBrand: {}, + localExtensionManagementServer: null, + remoteExtensionManagementServer, + getExtensionManagementServer: (location: URI) => { + if (location.scheme === REMOTE_HOST_SCHEME) { + return remoteExtensionManagementServer; + } + return null; + } + }; + } + + function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IExtensionManagementService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { + const localExtensionManagementServer: IExtensionManagementServer = { + authority: 'vscode-local', + label: 'local', + extensionManagementService: localExtensionManagementService || createExtensionManagementService() + }; + const remoteExtensionManagementServer: IExtensionManagementServer = { + authority: 'vscode-remote', + label: 'remote', + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + }; + return { + _serviceBrand: {}, + localExtensionManagementServer, + remoteExtensionManagementServer, + getExtensionManagementServer: (location: URI) => { + if (location.scheme === Schemas.file) { + return localExtensionManagementServer; + } + if (location.scheme === REMOTE_HOST_SCHEME) { + return remoteExtensionManagementServer; + } + return null; + } + }; + } + + function createExtensionManagementService(installed: ILocalExtension[] = []): IExtensionManagementService { + return { + onInstallExtension: Event.None, + onDidInstallExtension: Event.None, + onUninstallExtension: Event.None, + onDidUninstallExtension: Event.None, + getInstalled: () => Promise.resolve(installed), + installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')) + }; + } + }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 2476525146..b2b2c388e9 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -112,7 +112,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { test('test gallery extension', async () => { const expected = aGalleryExtension('expectedName', { displayName: 'expectedDisplayName', - version: '1.5', + version: '1.5.0', publisherId: 'expectedPublisherId', publisher: 'expectedPublisher', publisherDisplayName: 'expectedPublisherDisplayName', @@ -140,14 +140,14 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.equal(1, pagedResponse.firstPage.length); const actual = pagedResponse.firstPage[0]; - assert.equal(null, actual.type); + assert.equal(ExtensionType.User, actual.type); assert.equal('expectedName', actual.name); assert.equal('expectedDisplayName', actual.displayName); assert.equal('expectedpublisher.expectedname', actual.identifier.id); assert.equal('expectedPublisher', actual.publisher); assert.equal('expectedPublisherDisplayName', actual.publisherDisplayName); - assert.equal('1.5', actual.version); - assert.equal('1.5', actual.latestVersion); + assert.equal('1.5.0', actual.version); + assert.equal('1.5.0', actual.latestVersion); assert.equal('expectedDescription', actual.description); assert.equal('uri:icon', actual.iconUrl); assert.equal('fallback:icon', actual.iconUrlFallback); diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index d15394848c..40741dded7 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -5,7 +5,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; @@ -36,7 +36,7 @@ export class ExplorerService implements IExplorerService { private _onDidChangeEditable = new Emitter(); private _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>(); private _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>(); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; @@ -88,18 +88,18 @@ export class ExplorerService implements IExplorerService { (root?: URI) => getFileEventsExcludes(this.configurationService, root), (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) ); - this.disposables.push(fileEventsFilter); + this.disposables.add(fileEventsFilter); return fileEventsFilter; } @memoize get model(): ExplorerModel { const model = new ExplorerModel(this.contextService); - this.disposables.push(model); - this.disposables.push(this.fileService.onAfterOperation(e => this.onFileOperation(e))); - this.disposables.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { + this.disposables.add(model); + this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); + this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); + this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) { // A file system provider got re-registered, we should update all file stats since they might change (got read-only) this.model.roots.forEach(r => r.forgetChildren()); @@ -108,7 +108,7 @@ export class ExplorerService implements IExplorerService { this.fileSystemProviderSchemes.add(e.scheme); } })); - this.disposables.push(model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); + this.disposables.add(model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); return model; } @@ -380,6 +380,6 @@ export class ExplorerService implements IExplorerService { } dispose(): void { - dispose(this.disposables); + this.disposables.dispose(); } } diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 8c85b35f09..902d2b5805 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -11,7 +11,6 @@ import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -67,11 +66,9 @@ export class ToggleOrSetOutputScrollLockAction extends Action { public static readonly ID = 'workbench.output.action.toggleOutputScrollLock'; public static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock"); - private toDispose: IDisposable[] = []; - constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { super(id, label, 'output-action output-scroll-unlock'); - this.toDispose.push(this.outputService.onActiveOutputChannel(channel => { + this._register(this.outputService.onActiveOutputChannel(channel => { const activeChannel = this.outputService.getActiveChannel(); if (activeChannel) { this.setClassAndLabel(activeChannel.scrollLock); @@ -104,11 +101,6 @@ export class ToggleOrSetOutputScrollLockAction extends Action { this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off"); } } - - public dispose() { - super.dispose(); - this.toDispose = dispose(this.toDispose); - } } export class SwitchOutputAction extends Action { @@ -188,15 +180,13 @@ export class OpenLogOutputFile extends Action { public static readonly ID = 'workbench.output.action.openLogOutputFile'; public static readonly LABEL = nls.localize('openInLogViewer', "Open Log File"); - private disposables: IDisposable[] = []; - constructor( @IOutputService private readonly outputService: IOutputService, @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action open-log-file'); - this.outputService.onActiveOutputChannel(this.update, this, this.disposables); + this._register(this.outputService.onActiveOutputChannel(this.update, this)); this.update(); } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 71e7df9d2f..122594b1fd 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1151,6 +1151,10 @@ export class SearchView extends ViewletPanel { } onQueryChanged(preserveFocus?: boolean): void { + if (!this.searchWidget.searchInput.inputBox.isInputValid()) { + return; + } + const isRegex = this.searchWidget.searchInput.getRegex(); const isWholeWords = this.searchWidget.searchInput.getWholeWords(); const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive(); diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index 3c01869a7a..f6b387c526 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -3,25 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; import * as crypto from 'crypto'; import { onUnexpectedError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { IFileService, IFileStat, IResolveFileResult } from 'vs/platform/files/common/files'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWindowConfiguration, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; -import { Schemas } from 'vs/base/common/network'; -import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; -import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { joinPath } from 'vs/base/common/resources'; -import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -43,60 +36,6 @@ const SecondLevelDomainWhitelist = [ 'rhcloud.com', 'google.com' ]; -const ModulesToLookFor = [ - // Packages that suggest a node server - 'express', - 'sails', - 'koa', - 'hapi', - 'socket.io', - 'restify', - // JS frameworks - 'react', - 'react-native', - 'rnpm-plugin-windows', - '@angular/core', - '@ionic', - 'vue', - 'tns-core-modules', - // Other interesting packages - 'aws-sdk', - 'aws-amplify', - 'azure', - 'azure-storage', - 'firebase', - '@google-cloud/common', - 'heroku-cli' -]; -const PyModulesToLookFor = [ - 'azure', - 'azure-storage-common', - 'azure-storage-blob', - 'azure-storage-file', - 'azure-storage-queue', - 'azure-shell', - 'azure-cosmos', - 'azure-devtools', - 'azure-elasticluster', - 'azure-eventgrid', - 'azure-functions', - 'azure-graphrbac', - 'azure-keyvault', - 'azure-loganalytics', - 'azure-monitor', - 'azure-servicebus', - 'azure-servicefabric', - 'azure-storage', - 'azure-translator', - 'azure-iothub-device-client', - 'adal', - 'pydocumentdb', - 'botbuilder-core', - 'botbuilder-schema', - 'botframework-connector' -]; - -type Tags = { [index: string]: boolean | number | string | undefined }; function stripLowLevelDomains(domain: string): string | null { const match = domain.match(SecondLevelDomainMatcher); @@ -212,21 +151,14 @@ export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileSer export class WorkspaceStats implements IWorkbenchContribution { - static TAGS: Tags; - - private static DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen'; - constructor( @IFileService private readonly fileService: IFileService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWindowService private readonly windowService: IWindowService, - @INotificationService private readonly notificationService: INotificationService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IStorageService private readonly storageService: IStorageService, @ITextFileService private readonly textFileService: ITextFileService, - @ISharedProcessService private readonly sharedProcessService: ISharedProcessService + @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, + @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService ) { this.report(); } @@ -234,7 +166,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private report(): void { // Workspace Stats - this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles)) + this.workspaceStatsService.getTags() .then(tags => this.reportWorkspaceTags(tags), error => onUnexpectedError(error)); // Cloud Stats @@ -246,372 +178,7 @@ export class WorkspaceStats implements IWorkbenchContribution { diagnosticsChannel.call('reportWorkspaceStats', this.contextService.getWorkspace()); } - private static searchArray(arr: string[], regEx: RegExp): boolean | undefined { - return arr.some(v => v.search(regEx) > -1) || undefined; - } - /* __GDPR__FRAGMENT__ - "WorkspaceTags" : { - "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.empty" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.grunt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.gulp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.jake" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.tsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.jsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.config.xml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.vsc.extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.asp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.sln" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.unity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.express" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.sails" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.koa" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.hapi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.socket.io" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.restify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.rnpm-plugin-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.@angular/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.vue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.aws-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.aws-amplify-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.@google-cloud/common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.firebase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.npm.heroku-cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.cordova.low" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.xamarin.android" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.xamarin.ios" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.android.cpp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.reactNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.ionic" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" }, - "workspace.nativeScript" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" }, - "workspace.java.pom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.requirements" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.requirements.star" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.Pipfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.conda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.any-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-storage-common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-storage-blob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-storage-file" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-storage-queue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-mgmt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-shell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.pulumi-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-cosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-devtools" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-elasticluster" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-eventgrid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-functions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-graphrbac" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-keyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-loganalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-monitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-servicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-servicefabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-translator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-iothub-device-client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-ml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.adal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.pydocumentdb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.botbuilder-core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.botbuilder-schema" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.botframework-connector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - - } - */ - private resolveWorkspaceTags(configuration: IWindowConfiguration, participant?: (rootFiles: string[]) => void): Promise { - const tags: Tags = Object.create(null); - - const state = this.contextService.getWorkbenchState(); - const workspace = this.contextService.getWorkspace(); - - function createHash(uri: URI): string { - return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); - } - - let workspaceId: string | undefined; - switch (state) { - case WorkbenchState.EMPTY: - workspaceId = undefined; - break; - case WorkbenchState.FOLDER: - workspaceId = createHash(workspace.folders[0].uri); - break; - case WorkbenchState.WORKSPACE: - if (workspace.configuration) { - workspaceId = createHash(workspace.configuration); - } - } - - tags['workspace.id'] = workspaceId; - - const { filesToOpenOrCreate, filesToDiff } = configuration; - tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; - tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0; - - const isEmpty = state === WorkbenchState.EMPTY; - tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; - tags['workspace.empty'] = isEmpty; - - const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.environmentService.appQuality !== 'stable' && this.findFolders(configuration); - if (!folders || !folders.length || !this.fileService) { - return Promise.resolve(tags); - } - - return this.fileService.resolveAll(folders.map(resource => ({ resource }))).then((files: IResolveFileResult[]) => { - const names = ([]).concat(...files.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name); - const nameSet = names.reduce((s, n) => s.add(n.toLowerCase()), new Set()); - - if (participant) { - participant(names); - } - - tags['workspace.grunt'] = nameSet.has('gruntfile.js'); - tags['workspace.gulp'] = nameSet.has('gulpfile.js'); - tags['workspace.jake'] = nameSet.has('jakefile.js'); - - tags['workspace.tsconfig'] = nameSet.has('tsconfig.json'); - tags['workspace.jsconfig'] = nameSet.has('jsconfig.json'); - tags['workspace.config.xml'] = nameSet.has('config.xml'); - tags['workspace.vsc.extension'] = nameSet.has('vsc-extension-quickstart.md'); - - tags['workspace.ASP5'] = nameSet.has('project.json') && WorkspaceStats.searchArray(names, /^.+\.cs$/i); - tags['workspace.sln'] = WorkspaceStats.searchArray(names, /^.+\.sln$|^.+\.csproj$/i); - tags['workspace.unity'] = nameSet.has('assets') && nameSet.has('library') && nameSet.has('projectsettings'); - tags['workspace.npm'] = nameSet.has('package.json') || nameSet.has('node_modules'); - tags['workspace.bower'] = nameSet.has('bower.json') || nameSet.has('bower_components'); - - tags['workspace.java.pom'] = nameSet.has('pom.xml'); - - tags['workspace.yeoman.code.ext'] = nameSet.has('vsc-extension-quickstart.md'); - - tags['workspace.py.requirements'] = nameSet.has('requirements.txt'); - tags['workspace.py.requirements.star'] = WorkspaceStats.searchArray(names, /^(.*)requirements(.*)\.txt$/i); - tags['workspace.py.Pipfile'] = nameSet.has('pipfile'); - tags['workspace.py.conda'] = WorkspaceStats.searchArray(names, /^environment(\.yml$|\.yaml$)/i); - - const mainActivity = nameSet.has('mainactivity.cs') || nameSet.has('mainactivity.fs'); - const appDelegate = nameSet.has('appdelegate.cs') || nameSet.has('appdelegate.fs'); - const androidManifest = nameSet.has('androidmanifest.xml'); - - const platforms = nameSet.has('platforms'); - const plugins = nameSet.has('plugins'); - const www = nameSet.has('www'); - const properties = nameSet.has('properties'); - const resources = nameSet.has('resources'); - const jni = nameSet.has('jni'); - - if (tags['workspace.config.xml'] && - !tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) { - if (platforms && plugins && www) { - tags['workspace.cordova.high'] = true; - } else { - tags['workspace.cordova.low'] = true; - } - } - - if (tags['workspace.config.xml'] && - !tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) { - - if (nameSet.has('ionic.config.json')) { - tags['workspace.ionic'] = true; - } - } - - if (mainActivity && properties && resources) { - tags['workspace.xamarin.android'] = true; - } - - if (appDelegate && resources) { - tags['workspace.xamarin.ios'] = true; - } - - if (androidManifest && jni) { - tags['workspace.android.cpp'] = true; - } - - function getFilePromises(filename: string, fileService: IFileService, textFileService: ITextFileService, contentHandler: (content: ITextFileContent) => void): Promise[] { - return !nameSet.has(filename) ? [] : (folders as URI[]).map(workspaceUri => { - const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/${filename}` }); - return fileService.exists(uri).then(exists => { - if (!exists) { - return undefined; - } - - return textFileService.read(uri, { acceptTextOnly: true }).then(contentHandler); - }, err => { - // Ignore missing file - }); - }); - } - - function addPythonTags(packageName: string): void { - if (PyModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.py.' + packageName] = true; - } - // cognitive services has a lot of tiny packages. e.g. 'azure-cognitiveservices-search-autosuggest' - if (packageName.indexOf('azure-cognitiveservices') > -1) { - tags['workspace.py.azure-cognitiveservices'] = true; - } - if (packageName.indexOf('azure-mgmt') > -1) { - tags['workspace.py.azure-mgmt'] = true; - } - if (packageName.indexOf('azure-ml') > -1) { - tags['workspace.py.azure-ml'] = true; - } - if (!tags['workspace.py.any-azure']) { - tags['workspace.py.any-azure'] = /azure/i.test(packageName); - } - } - - const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => { - const dependencies: string[] = content.value.split(/\r\n|\r|\n/); - for (let dependency of dependencies) { - // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` - const format1 = dependency.split('=='); - const format2 = dependency.split('>='); - const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim(); - addPythonTags(packageName); - } - }); - - const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => { - let dependencies: string[] = content.value.split(/\r\n|\r|\n/); - - // We're only interested in the '[packages]' section of the Pipfile - dependencies = dependencies.slice(dependencies.indexOf('[packages]') + 1); - - for (let dependency of dependencies) { - if (dependency.trim().indexOf('[') > -1) { - break; - } - // All dependencies in Pipfiles follow the format: ` = ` - if (dependency.indexOf('=') === -1) { - continue; - } - const packageName = dependency.split('=')[0].trim(); - addPythonTags(packageName); - } - - }); - - const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => { - try { - const packageJsonContents = JSON.parse(content.value); - let dependencies = packageJsonContents['dependencies']; - let devDependencies = packageJsonContents['devDependencies']; - for (let module of ModulesToLookFor) { - if ('react-native' === module) { - if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { - tags['workspace.reactNative'] = true; - } - } else if ('tns-core-modules' === module) { - if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { - tags['workspace.nativescript'] = true; - } - } else { - if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { - tags['workspace.npm.' + module] = true; - } - } - } - - } - catch (e) { - // Ignore errors when resolving file or parsing file contents - } - }); - return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises]).then(() => tags); - }); - } - - private handleWorkspaceFiles(rootFiles: string[]): void { - const state = this.contextService.getWorkbenchState(); - const workspace = this.contextService.getWorkspace(); - - // Handle top-level workspace files for local single folder workspace - if (state === WorkbenchState.FOLDER) { - const workspaceFiles = rootFiles.filter(hasWorkspaceFileExtension); - if (workspaceFiles.length > 0) { - this.doHandleWorkspaceFiles(workspace.folders[0].uri, workspaceFiles); - } - } - } - - private doHandleWorkspaceFiles(folder: URI, workspaces: string[]): void { - if (this.storageService.getBoolean(WorkspaceStats.DISABLE_WORKSPACE_PROMPT_KEY, StorageScope.WORKSPACE)) { - return; // prompt disabled by user - } - - const doNotShowAgain: IPromptChoice = { - label: localize('never again', "Don't Show Again"), - isSecondary: true, - run: () => this.storageService.store(WorkspaceStats.DISABLE_WORKSPACE_PROMPT_KEY, true, StorageScope.WORKSPACE) - }; - - // Prompt to open one workspace - if (workspaces.length === 1) { - const workspaceFile = workspaces[0]; - - this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ - label: localize('openWorkspace', "Open Workspace"), - run: () => this.windowService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }]) - }, doNotShowAgain]); - } - - // Prompt to select a workspace from many - else if (workspaces.length > 1) { - this.notificationService.prompt(Severity.Info, localize('workspacesFound', "This folder contains multiple workspace files. Do you want to open one? [Learn more]({0}) about workspace files.", 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ - label: localize('selectWorkspace', "Select Workspace"), - run: () => { - this.quickInputService.pick( - workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)), - { placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => { - if (pick) { - this.windowService.openWindow([{ workspaceUri: joinPath(folder, pick.label) }]); - } - }); - } - }, doNotShowAgain]); - } - } - - private findFolders(configuration: IWindowConfiguration): URI[] | undefined { - const folder = this.findFolder(configuration); - return folder && [folder]; - } - - private findFolder({ filesToOpenOrCreate, filesToDiff }: IWindowConfiguration): URI | undefined { - if (filesToOpenOrCreate && filesToOpenOrCreate.length) { - return this.parentURI(filesToOpenOrCreate[0].fileUri); - } else if (filesToDiff && filesToDiff.length) { - return this.parentURI(filesToDiff[0].fileUri); - } - return undefined; - } - - private parentURI(uri: URI | undefined): URI | undefined { - if (!uri) { - return undefined; - } - const path = uri.path; - const i = path.lastIndexOf('/'); - return i !== -1 ? uri.with({ path: path.substr(0, i) }) : undefined; - } private reportWorkspaceTags(tags: Tags): void { /* __GDPR__ @@ -622,7 +189,6 @@ export class WorkspaceStats implements IWorkbenchContribution { } */ this.telemetryService.publicLog('workspce.tags', tags); - WorkspaceStats.TAGS = tags; } private reportRemoteDomains(workspaceUris: URI[]): void { @@ -689,6 +255,10 @@ export class WorkspaceStats implements IWorkbenchContribution { }); } + private static searchArray(arr: string[], regEx: RegExp): boolean | undefined { + return arr.some(v => v.search(regEx) > -1) || undefined; + } + /* __GDPR__FRAGMENT__ "AzureTags" : { "java" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts new file mode 100644 index 0000000000..18be93ac57 --- /dev/null +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts @@ -0,0 +1,477 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { localize } from 'vs/nls'; +import Severity from 'vs/base/common/severity'; +import { joinPath } from 'vs/base/common/resources'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export type Tags = { [index: string]: boolean | number | string | undefined }; + +const DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen'; + +const ModulesToLookFor = [ + // Packages that suggest a node server + 'express', + 'sails', + 'koa', + 'hapi', + 'socket.io', + 'restify', + // JS frameworks + 'react', + 'react-native', + 'rnpm-plugin-windows', + '@angular/core', + '@ionic', + 'vue', + 'tns-core-modules', + // Other interesting packages + 'aws-sdk', + 'aws-amplify', + 'azure', + 'azure-storage', + 'firebase', + '@google-cloud/common', + 'heroku-cli' +]; +const PyModulesToLookFor = [ + 'azure', + 'azure-storage-common', + 'azure-storage-blob', + 'azure-storage-file', + 'azure-storage-queue', + 'azure-shell', + 'azure-cosmos', + 'azure-devtools', + 'azure-elasticluster', + 'azure-eventgrid', + 'azure-functions', + 'azure-graphrbac', + 'azure-keyvault', + 'azure-loganalytics', + 'azure-monitor', + 'azure-servicebus', + 'azure-servicefabric', + 'azure-storage', + 'azure-translator', + 'azure-iothub-device-client', + 'adal', + 'pydocumentdb', + 'botbuilder-core', + 'botbuilder-schema', + 'botframework-connector' +]; + +export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); + +export interface IWorkspaceStatsService { + _serviceBrand: any; + getTags(): Promise; +} + + +export class WorkspaceStatsService implements IWorkspaceStatsService { + _serviceBrand: any; + private _tags: Tags; + + constructor( + @IFileService private readonly fileService: IFileService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWindowService private readonly windowService: IWindowService, + @INotificationService private readonly notificationService: INotificationService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IStorageService private readonly storageService: IStorageService, + @ITextFileService private readonly textFileService: ITextFileService + ) { } + + public async getTags(): Promise { + if (!this._tags) { + this._tags = await this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles)); + } + + return this._tags; + } + + /* __GDPR__FRAGMENT__ + "WorkspaceTags" : { + "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.empty" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.grunt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gulp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.jake" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.tsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.jsconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.config.xml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.vsc.extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.asp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.sln" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.unity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.express" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.sails" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.koa" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.hapi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.socket.io" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.restify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.rnpm-plugin-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@angular/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.vue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.aws-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.aws-amplify-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@google-cloud/common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.firebase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.heroku-cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.cordova.low" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.xamarin.android" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.xamarin.ios" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.android.cpp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.reactNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.ionic" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" }, + "workspace.nativeScript" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" }, + "workspace.java.pom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.requirements" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.requirements.star" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.Pipfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.conda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.any-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-storage-common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-storage-blob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-storage-file" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-storage-queue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-mgmt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-shell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.pulumi-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-devtools" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-elasticluster" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-eventgrid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-functions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-graphrbac" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-keyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-loganalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-monitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-servicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-servicefabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-translator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-iothub-device-client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.adal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.pydocumentdb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.botbuilder-core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.botbuilder-schema" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.botframework-connector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + + } + */ + private resolveWorkspaceTags(configuration: IWindowConfiguration, participant?: (rootFiles: string[]) => void): Promise { + const tags: Tags = Object.create(null); + + const state = this.contextService.getWorkbenchState(); + const workspace = this.contextService.getWorkspace(); + + function createHash(uri: URI): string { + return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); + } + + let workspaceId: string | undefined; + switch (state) { + case WorkbenchState.EMPTY: + workspaceId = undefined; + break; + case WorkbenchState.FOLDER: + workspaceId = createHash(workspace.folders[0].uri); + break; + case WorkbenchState.WORKSPACE: + if (workspace.configuration) { + workspaceId = createHash(workspace.configuration); + } + } + + tags['workspace.id'] = workspaceId; + + const { filesToOpenOrCreate, filesToDiff } = configuration; + tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; + tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0; + + const isEmpty = state === WorkbenchState.EMPTY; + tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; + tags['workspace.empty'] = isEmpty; + + const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.environmentService.appQuality !== 'stable' && this.findFolders(configuration); + if (!folders || !folders.length || !this.fileService) { + return Promise.resolve(tags); + } + + return this.fileService.resolveAll(folders.map(resource => ({ resource }))).then((files: IResolveFileResult[]) => { + const names = ([]).concat(...files.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name); + const nameSet = names.reduce((s, n) => s.add(n.toLowerCase()), new Set()); + + if (participant) { + participant(names); + } + + tags['workspace.grunt'] = nameSet.has('gruntfile.js'); + tags['workspace.gulp'] = nameSet.has('gulpfile.js'); + tags['workspace.jake'] = nameSet.has('jakefile.js'); + + tags['workspace.tsconfig'] = nameSet.has('tsconfig.json'); + tags['workspace.jsconfig'] = nameSet.has('jsconfig.json'); + tags['workspace.config.xml'] = nameSet.has('config.xml'); + tags['workspace.vsc.extension'] = nameSet.has('vsc-extension-quickstart.md'); + + tags['workspace.ASP5'] = nameSet.has('project.json') && this.searchArray(names, /^.+\.cs$/i); + tags['workspace.sln'] = this.searchArray(names, /^.+\.sln$|^.+\.csproj$/i); + tags['workspace.unity'] = nameSet.has('assets') && nameSet.has('library') && nameSet.has('projectsettings'); + tags['workspace.npm'] = nameSet.has('package.json') || nameSet.has('node_modules'); + tags['workspace.bower'] = nameSet.has('bower.json') || nameSet.has('bower_components'); + + tags['workspace.java.pom'] = nameSet.has('pom.xml'); + + tags['workspace.yeoman.code.ext'] = nameSet.has('vsc-extension-quickstart.md'); + + tags['workspace.py.requirements'] = nameSet.has('requirements.txt'); + tags['workspace.py.requirements.star'] = this.searchArray(names, /^(.*)requirements(.*)\.txt$/i); + tags['workspace.py.Pipfile'] = nameSet.has('pipfile'); + tags['workspace.py.conda'] = this.searchArray(names, /^environment(\.yml$|\.yaml$)/i); + + const mainActivity = nameSet.has('mainactivity.cs') || nameSet.has('mainactivity.fs'); + const appDelegate = nameSet.has('appdelegate.cs') || nameSet.has('appdelegate.fs'); + const androidManifest = nameSet.has('androidmanifest.xml'); + + const platforms = nameSet.has('platforms'); + const plugins = nameSet.has('plugins'); + const www = nameSet.has('www'); + const properties = nameSet.has('properties'); + const resources = nameSet.has('resources'); + const jni = nameSet.has('jni'); + + if (tags['workspace.config.xml'] && + !tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) { + if (platforms && plugins && www) { + tags['workspace.cordova.high'] = true; + } else { + tags['workspace.cordova.low'] = true; + } + } + + if (tags['workspace.config.xml'] && + !tags['workspace.language.cs'] && !tags['workspace.language.vb'] && !tags['workspace.language.aspx']) { + + if (nameSet.has('ionic.config.json')) { + tags['workspace.ionic'] = true; + } + } + + if (mainActivity && properties && resources) { + tags['workspace.xamarin.android'] = true; + } + + if (appDelegate && resources) { + tags['workspace.xamarin.ios'] = true; + } + + if (androidManifest && jni) { + tags['workspace.android.cpp'] = true; + } + + function getFilePromises(filename: string, fileService: IFileService, textFileService: ITextFileService, contentHandler: (content: ITextFileContent) => void): Promise[] { + return !nameSet.has(filename) ? [] : (folders as URI[]).map(workspaceUri => { + const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/${filename}` }); + return fileService.exists(uri).then(exists => { + if (!exists) { + return undefined; + } + + return textFileService.read(uri, { acceptTextOnly: true }).then(contentHandler); + }, err => { + // Ignore missing file + }); + }); + } + + function addPythonTags(packageName: string): void { + if (PyModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.py.' + packageName] = true; + } + // cognitive services has a lot of tiny packages. e.g. 'azure-cognitiveservices-search-autosuggest' + if (packageName.indexOf('azure-cognitiveservices') > -1) { + tags['workspace.py.azure-cognitiveservices'] = true; + } + if (packageName.indexOf('azure-mgmt') > -1) { + tags['workspace.py.azure-mgmt'] = true; + } + if (packageName.indexOf('azure-ml') > -1) { + tags['workspace.py.azure-ml'] = true; + } + if (!tags['workspace.py.any-azure']) { + tags['workspace.py.any-azure'] = /azure/i.test(packageName); + } + } + + const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => { + const dependencies: string[] = content.value.split(/\r\n|\r|\n/); + for (let dependency of dependencies) { + // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` + const format1 = dependency.split('=='); + const format2 = dependency.split('>='); + const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim(); + addPythonTags(packageName); + } + }); + + const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => { + let dependencies: string[] = content.value.split(/\r\n|\r|\n/); + + // We're only interested in the '[packages]' section of the Pipfile + dependencies = dependencies.slice(dependencies.indexOf('[packages]') + 1); + + for (let dependency of dependencies) { + if (dependency.trim().indexOf('[') > -1) { + break; + } + // All dependencies in Pipfiles follow the format: ` = ` + if (dependency.indexOf('=') === -1) { + continue; + } + const packageName = dependency.split('=')[0].trim(); + addPythonTags(packageName); + } + + }); + + const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => { + try { + const packageJsonContents = JSON.parse(content.value); + let dependencies = packageJsonContents['dependencies']; + let devDependencies = packageJsonContents['devDependencies']; + for (let module of ModulesToLookFor) { + if ('react-native' === module) { + if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { + tags['workspace.reactNative'] = true; + } + } else if ('tns-core-modules' === module) { + if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { + tags['workspace.nativescript'] = true; + } + } else { + if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { + tags['workspace.npm.' + module] = true; + } + } + } + + } + catch (e) { + // Ignore errors when resolving file or parsing file contents + } + }); + return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises]).then(() => tags); + }); + } + + private handleWorkspaceFiles(rootFiles: string[]): void { + const state = this.contextService.getWorkbenchState(); + const workspace = this.contextService.getWorkspace(); + + // Handle top-level workspace files for local single folder workspace + if (state === WorkbenchState.FOLDER) { + const workspaceFiles = rootFiles.filter(hasWorkspaceFileExtension); + if (workspaceFiles.length > 0) { + this.doHandleWorkspaceFiles(workspace.folders[0].uri, workspaceFiles); + } + } + } + + private doHandleWorkspaceFiles(folder: URI, workspaces: string[]): void { + if (this.storageService.getBoolean(DISABLE_WORKSPACE_PROMPT_KEY, StorageScope.WORKSPACE)) { + return; // prompt disabled by user + } + + const doNotShowAgain: IPromptChoice = { + label: localize('never again', "Don't Show Again"), + isSecondary: true, + run: () => this.storageService.store(DISABLE_WORKSPACE_PROMPT_KEY, true, StorageScope.WORKSPACE) + }; + + // Prompt to open one workspace + if (workspaces.length === 1) { + const workspaceFile = workspaces[0]; + + this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ + label: localize('openWorkspace', "Open Workspace"), + run: () => this.windowService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }]) + }, doNotShowAgain]); + } + + // Prompt to select a workspace from many + else if (workspaces.length > 1) { + this.notificationService.prompt(Severity.Info, localize('workspacesFound', "This folder contains multiple workspace files. Do you want to open one? [Learn more]({0}) about workspace files.", 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ + label: localize('selectWorkspace', "Select Workspace"), + run: () => { + this.quickInputService.pick( + workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)), + { placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => { + if (pick) { + this.windowService.openWindow([{ workspaceUri: joinPath(folder, pick.label) }]); + } + }); + } + }, doNotShowAgain]); + } + } + + private findFolders(configuration: IWindowConfiguration): URI[] | undefined { + const folder = this.findFolder(configuration); + return folder && [folder]; + } + + private findFolder({ filesToOpenOrCreate, filesToDiff }: IWindowConfiguration): URI | undefined { + if (filesToOpenOrCreate && filesToOpenOrCreate.length) { + return this.parentURI(filesToOpenOrCreate[0].fileUri); + } else if (filesToDiff && filesToDiff.length) { + return this.parentURI(filesToDiff[0].fileUri); + } + return undefined; + } + + private parentURI(uri: URI | undefined): URI | undefined { + if (!uri) { + return undefined; + } + const path = uri.path; + const i = path.lastIndexOf('/'); + return i !== -1 ? uri.with({ path: path.substr(0, i) }) : undefined; + } + + private searchArray(arr: string[], regEx: RegExp): boolean | undefined { + return arr.some(v => v.search(regEx) > -1) || undefined; + } +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 3841104125..b460eeaf33 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -9,6 +9,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview'; +import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; class WebviewIconsManager { private readonly _icons = new Map(); @@ -58,6 +59,7 @@ export class WebviewEditorInput extends EditorInput { private _name: string; private _iconPath?: { light: URI, dark: URI }; private _group?: GroupIdentifier; + private readonly _webview: WebviewEditorOverlay; constructor( public readonly id: string, @@ -67,14 +69,14 @@ export class WebviewEditorInput extends EditorInput { readonly location: URI; readonly id: ExtensionIdentifier; }, - public readonly webview: WebviewEditorOverlay, + webview: Unowned, ) { super(); this._name = name; this.extension = extension; - this._register(webview); // The input owns this webview + this._webview = this._register(webview.acquire()); // The input owns this webview } public getTypeId(): string { @@ -105,6 +107,10 @@ export class WebviewEditorInput extends EditorInput { this._onDidChangeLabel.fire(); } + public get webview() { + return this._webview; + } + public get iconPath() { return this._iconPath; } @@ -147,7 +153,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput { readonly id: ExtensionIdentifier }, private readonly reviver: (input: WebviewEditorInput) => Promise, - webview: WebviewEditorOverlay, + webview: Unowned, ) { super(id, viewType, name, extension, webview); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 6d3731fb80..085d9b45dc 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equals } from 'vs/base/common/arrays'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, UnownedDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -144,7 +144,7 @@ export class WebviewEditorService implements IWebviewEditorService { ): WebviewEditorInput { const webview = this.createWebiew(id, extension, options); - const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, webview); + const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, new UnownedDisposable(webview)); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group); return webviewInput; } @@ -191,7 +191,7 @@ export class WebviewEditorService implements IWebviewEditorService { const promise = new Promise(r => { resolve = r; }); this._revivalPool.add(webview, resolve!); return promise; - }, webview); + }, new UnownedDisposable(webview)); webviewInput.iconPath = iconPath; diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 9280fb48bf..7700987a66 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -237,6 +237,13 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } return getFilePath(); + case 'relativeFileDirname': + let dirname = paths.dirname(getFilePath()); + if (folderUri) { + return paths.normalize(paths.relative(getFolderUri().fsPath, dirname)); + } + return dirname; + case 'fileDirname': return paths.dirname(getFilePath()); diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index b1302e920d..9809ef95a4 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -221,7 +221,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = [schema]; // by default only allow saving in the own file system + options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well } return this.saveRemoteResource(options); @@ -239,7 +239,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = [schema]; // by default only allow loading in the own file system + options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well } const uri = await this.pickRemoteResource(options); diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index fad4872759..93b1c12748 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -14,7 +14,7 @@ import { ExtHostCustomersRegistry } from 'vs/workbench/api/common/extHostCustome import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; @@ -249,15 +249,17 @@ export class ExtensionHostProcessManager extends Disposable { return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort()); } - public async resolveAuthority(remoteAuthority: string): Promise { + public async resolveAuthority(remoteAuthority: string): Promise { const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { // This authority does not need to be resolved, simply parse the port number const pieces = remoteAuthority.split(':'); return Promise.resolve({ - authority: remoteAuthority, - host: pieces[0], - port: parseInt(pieces[1], 10) + authority: { + authority: remoteAuthority, + host: pieces[0], + port: parseInt(pieces[1], 10) + } }); } const proxy = await this._getExtensionHostProcessProxy(); diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 9c4bf65afb..434c78f21f 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -76,19 +76,20 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH webSocketFactory: this._webSocketFactory, addressProvider: { getAddress: async () => { - const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); - return { host, port }; + const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); + return { host: authority.host, port: authority.port }; } }, signService: this._signService }; - return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => { + return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => { const startParams: IRemoteExtensionHostStartParams = { language: platform.language, debugId: this._environmentService.debugExtensionHost.debugId, break: this._environmentService.debugExtensionHost.break, port: this._environmentService.debugExtensionHost.port, + env: resolverResult.options && resolverResult.options.extensionHostEnv }; const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index ea2c188a24..87cd720218 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -19,7 +19,7 @@ import { IExtensionEnablementService } from 'vs/workbench/services/extensionMana import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -424,8 +424,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten const extensionHost = this._extensionHostProcessManagers[0]; this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority); try { - const resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority); - this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority); + const result = await extensionHost.resolveAuthority(remoteAuthority); + this._remoteAuthorityResolverService.setResolvedAuthority(result.authority, result.options); } catch (err) { this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err); } @@ -446,7 +446,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten localExtensions = localExtensions.filter(extension => this._isEnabled(extension)); if (remoteAuthority) { - let resolvedAuthority: ResolvedAuthority; + let resolvedAuthority: ResolverResult; try { resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority); @@ -468,7 +468,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } // set the resolved authority - this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority); + this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options); // monitor for breakage const connection = this._remoteAgentService.getConnection(); diff --git a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts index 9d66817e23..eeb15b5d53 100644 --- a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts @@ -12,33 +12,39 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; class TestKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase { constructor(notificationService: INotificationService, storageService: IStorageService, commandService: ICommandService) { super(notificationService, storageService, commandService); - let keymapInfos: IKeymapInfo[] = KeyboardLayoutContribution.INSTANCE.layoutInfos; + const keymapInfos: IKeymapInfo[] = KeyboardLayoutContribution.INSTANCE.layoutInfos; this._keymapInfos.push(...keymapInfos.map(info => (new KeymapInfo(info.layout, info.secondaryLayouts, info.mapping, info.isUserKeyboardLayout)))); this._mru = this._keymapInfos; this._initialized = true; this.onKeyboardLayoutChanged(); + const usLayout = this.getUSStandardLayout(); + if (usLayout) { + this.setActiveKeyMapping(usLayout.mapping); + } } } - suite('keyboard layout loader', () => { let instantiationService: TestInstantiationService = new TestInstantiationService(); - let notitifcationService = instantiationService.stub(INotificationService, {}); - let storageService = instantiationService.stub(IStorageService, {}); + let notitifcationService = instantiationService.stub(INotificationService, new TestNotificationService()); + let storageService = instantiationService.stub(IStorageService, new TestStorageService()); + let commandService = instantiationService.stub(ICommandService, {}); let instance = new TestKeyboardMapperFactory(notitifcationService, storageService, commandService); - test.skip('load default US keyboard layout', () => { + test('load default US keyboard layout', () => { assert.notEqual(instance.activeKeyboardLayout, null); assert.equal(instance.activeKeyboardLayout!.isUSStandard, true); }); - test.skip('isKeyMappingActive', () => { + test('isKeyMappingActive', () => { assert.equal(instance.isKeyMappingActive({ KeyA: { value: 'a', diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 0db97264ba..16faf5df17 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -121,8 +121,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon } else { this._onReconnecting.fire(undefined); } - const { host, port } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); - return { host, port }; + const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority); + return { host: authority.host, port: authority.port }; } }, signService: this._signService diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index ed6caeb7e5..4251b35597 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -105,8 +105,8 @@ export class TunnelService implements ITunnelService { webSocketFactory: nodeWebSocketFactory, addressProvider: { getAddress: async () => { - const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); - return { host, port }; + const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); + return { host: authority.host, port: authority.port }; } }, signService: this.signService diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index d4bae700c1..55aee3699b 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -168,6 +168,7 @@ registerSingleton(IMenubarService, MenubarService); registerSingleton(IURLService, RelayURLService); registerSingleton(ITunnelService, TunnelService, true); registerSingleton(ICredentialsService, KeytarCredentialsService, true); +registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); //#endregion @@ -456,6 +457,7 @@ import 'vs/workbench/contrib/experiments/electron-browser/experiments.contributi // Issues import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; +import { IWorkspaceStatsService, WorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; // {{SQL CARBON EDIT}} // SQL diff --git a/yarn.lock b/yarn.lock index 3751bf6d8d..aa68c2eb4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9902,10 +9902,10 @@ vscode-proxy-agent@0.4.0: https-proxy-agent "2.2.1" socks-proxy-agent "4.0.1" -vscode-ripgrep@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.4.tgz#dae1c3eef350513299341cdf96e622c00b548eff" - integrity sha512-Bs8SvFAkR0QHf09J46VgNo19yRikOtj/f0zHzK3AM3ICjCGNN/BNoG9of6zGVHUTO+6Mk1RbKglyOHLKr8D1lg== +vscode-ripgrep@^1.5.5: + version "1.5.5" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" + integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== vscode-sqlite3@4.0.8: version "4.0.8"