diff --git a/.eslintrc.json b/.eslintrc.json index 0abb18adee2..8761c1c1813 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -725,6 +725,18 @@ "*" // node modules ] }, + { + "target": "**/vs/code/browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser}/**", + "**/vs/base/parts/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/code/**/{common,browser}/**", + "**/vs/workbench/workbench.web.api" + ] + }, { "target": "**/vs/code/node/**", "restrictions": [ @@ -795,6 +807,18 @@ "**/vs/workbench/workbench.common.main" ] }, + { + "target": "**/src/vs/workbench/workbench.web.api.ts", + "restrictions": [ + "vs/nls", + "**/vs/base/**/{common,browser}/**", + "**/vs/base/parts/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/editor/**", + "**/vs/workbench/**/{common,browser}/**", + "**/vs/workbench/workbench.web.main" + ] + }, { "target": "**/src/vs/workbench/workbench.sandbox.main.ts", "restrictions": [ diff --git a/.github/commands.json b/.github/commands.json index 45e8b89deae..7d93e109e10 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -351,6 +351,18 @@ "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Java Debugger extension. Please file it with the repository [here](https://github.com/Microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" }, + { + "type": "comment", + "name": "gifPlease", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "comment", + "comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette).\n\nHappy coding!" + }, { "type": "comment", "name": "label", diff --git a/.github/workflows/feature-request.yml b/.github/workflows/feature-request.yml index 893bba74a16..62962bc3e98 100644 --- a/.github/workflows/feature-request.yml +++ b/.github/workflows/feature-request.yml @@ -34,8 +34,8 @@ jobs: featureRequestLabel: feature-request upvotesRequired: 20 numCommentsOverride: 20 - initComment: "This feature request is now a candidate for our backlog. The community has 60 days to upvote the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" - warnComment: "This feature request has not yet received the 20 community upvotes it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding" + initComment: "This feature request is now a candidate for our backlog. The community has 60 days to [upvote](https://github.com/microsoft/vscode/wiki/Issues-Triaging#up-voting-a-feature-request) the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" + warnComment: "This feature request has not yet received the 20 community [upvotes](https://github.com/microsoft/vscode/wiki/Issues-Triaging#up-voting-a-feature-request) it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding" acceptComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" rejectComment: ":slightly_frowning_face: In the last 60 days, this feature request has received less than 20 community upvotes and we closed it. Still a big Thank You to you for taking the time to create this issue! To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" warnDays: 10 diff --git a/.yarnrc b/.yarnrc index 4c5125d8923..5119ded1027 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "8.3.3" +target "9.1.0" runtime "electron" diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 5785de63367..eccd74db1c6 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index ea286ef1418..7505e5ba9da 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 4689451b54e..f9bdf7fef8e 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -8,7 +8,7 @@ pr: steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 370c56fa6a1..a8747353c37 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -11,7 +11,7 @@ pr: steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index fdd4c305cda..0cffb4ba266 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -10,7 +10,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index 485f8dcfba7..258f87ea3d2 100644 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ b/build/azure-pipelines/linux/product-build-linux-multiarch.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 5d7bccf467f..dbd0621a273 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index a530499b313..39c39e86c9e 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index db6524be03b..ab0dbb932c6 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -16,7 +16,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index b73cd04a966..10b6aa4e16a 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -9,7 +9,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index 2641830a413..49dfc9ced80 100644 --- a/build/azure-pipelines/sync-mooncake.yml +++ b/build/azure-pipelines/sync-mooncake.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 0c338203b4d..7f4907aa2d9 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 026a162f510..cbe4a8ac85f 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/win32/product-build-win32-arm64.yml b/build/azure-pipelines/win32/product-build-win32-arm64.yml index 01be34aa9a8..ecb50ad678e 100644 --- a/build/azure-pipelines/win32/product-build-win32-arm64.yml +++ b/build/azure-pipelines/win32/product-build-win32-arm64.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index fb4f3052578..c233d5c6d29 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "12.13.0" + versionSpec: "12.14.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index bbb7e60e699..60777cb42cc 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -78,7 +78,7 @@ const vscodeResources = [ 'out-build/vs/code/electron-browser/workbench/**', 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-browser/issue/issueReporter.js', - 'out-build/vs/code/electron-browser/processExplorer/processExplorer.js', + 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js', 'out-build/vs/platform/auth/common/auth.css', '!**/test/**' ]; diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 9bba404c243..ceef664b85f 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -246,10 +246,6 @@ "name": "vs/workbench/services/configurationResolver", "project": "vscode-workbench" }, - { - "name": "vs/workbench/services/crashReporter", - "project": "vscode-workbench" - }, { "name": "vs/workbench/services/dialogs", "project": "vscode-workbench" diff --git a/build/yarn.lock b/build/yarn.lock index a0ac8823544..b0e68408127 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1749,9 +1749,9 @@ lodash.unescape@4.0.1: integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= lodash@^4.15.0, lodash@^4.17.10: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== macos-release@^2.2.0: version "2.3.0" diff --git a/cgmanifest.json b/cgmanifest.json index 1fb78691e71..29afebf6b08 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "052d3b44972e6d94ef40054d46c150b7cdd7a5d8" + "commitHash": "894fb9eb56c6cbda65e3c3ae9ada6d4cb5850cc9" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "80.0.3987.165" + "version": "83.0.4103.122" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "42cce5a9d0fd905bf4ad7a2528c36572dfb8b5ad" + "commitHash": "9622fed3fb2cffcea9efff6c8cb4cc2def99d75d" } }, "isOnlyProductionDependency": true, - "version": "12.13.0" + "version": "12.14.1" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "87fd06bc96bce8f46ca05b8315657fd230bcac85" + "commitHash": "a822d2639a9c9c2c670e91d73f78e921865ce38e" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "8.3.3" + "version": "9.1.0" }, { "component": { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 0a0c3c319cb..26d1a9ecbde 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -414,15 +414,10 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash@^4.16.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== - -lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.16.4, lodash@^4.17.15: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@3.0.0: version "3.0.0" diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index ca01c59ff1c..06a8845658c 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -1503,9 +1503,9 @@ lodash.values@~2.4.1: lodash.keys "~2.4.1" lodash@^4.16.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== loud-rejection@^1.0.0: version "1.6.0" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 0a4c850d7a3..c4189e6b7eb 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -12,7 +12,8 @@ "Other" ], "activationEvents": [ - "*" + "*", + "onAuthenticationRequest:github" ], "contributes": { "commands": [ diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 550e68e37e3..fcae1cf9a14 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -67,15 +67,26 @@ function parseQuery(uri: vscode.Uri) { export class GitHubServer { private _statusBarItem: vscode.StatusBarItem | undefined; + private isTestEnvironment(url: vscode.Uri): boolean { + return url.authority === 'vscode-web-test-playground.azurewebsites.net' || url.authority.startsWith('localhost:'); + } + public async login(scopes: string): Promise { Logger.info('Logging in...'); this.updateStatusBarItem(true); const state = uuid(); const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); - const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com`); - await vscode.env.openExternal(uri); + if (this.isTestEnvironment(callbackUri)) { + const token = await vscode.window.showInputBox({ prompt: 'Token', ignoreFocusOut: true }); + if (!token) { throw new Error('Sign in failed: No token provided'); } + this.updateStatusBarItem(false); + return token; + } else { + const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com`); + await vscode.env.openExternal(uri); + } return Promise.race([ promiseFromEvent(uriHandler.event, exchangeCodeForToken(state)), diff --git a/extensions/html-language-features/server/src/test/completions.test.ts b/extensions/html-language-features/server/src/test/completions.test.ts index b491ad28836..0371a2f74cd 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -57,7 +57,7 @@ export async function testCompletionFor(value: string, expected: { count?: numbe let document = TextDocument.create(uri, 'html', 0, value); let position = document.positionAt(offset); - const context = getDocumentContext(uri, workspace.folders) + const context = getDocumentContext(uri, workspace.folders); const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); const mode = languageModes.getModeAtPosition(document, position)!; diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 0778b5468ac..42cef380a66 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -418,15 +418,10 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash@^4.16.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== - -lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.16.4, lodash@^4.17.15: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@3.0.0: version "3.0.0" diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 23cd1744005..7687bd3d14b 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -2773,9 +2773,9 @@ lodash.throttle@^4.1.1: integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= lodash@^4.16.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== lru-cache@^5.1.1: version "5.1.1" diff --git a/extensions/microsoft-authentication/extension-browser.webpack.config.js b/extensions/microsoft-authentication/extension-browser.webpack.config.js index 5d4b88ed221..d2f4d9ee94b 100644 --- a/extensions/microsoft-authentication/extension-browser.webpack.config.js +++ b/extensions/microsoft-authentication/extension-browser.webpack.config.js @@ -22,6 +22,7 @@ module.exports = withBrowserDefaults({ resolve: { alias: { './env/node': path.resolve(__dirname, 'src/env/browser'), + './authServer': path.resolve(__dirname, 'src/env/browser/authServer'), 'buffer': path.resolve(__dirname, 'node_modules/buffer/index.js'), 'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js'), 'randombytes': path.resolve(__dirname, 'node_modules/randombytes/browser.js'), diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 4300598429d..0794d50127b 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -12,7 +12,8 @@ ], "enableProposedApi": true, "activationEvents": [ - "*" + "*", + "onAuthenticationRequest:microsoft" ], "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index b553b1a64ca..24599f62b22 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -6,7 +6,7 @@ import * as randomBytes from 'randombytes'; import * as querystring from 'querystring'; import * as vscode from 'vscode'; -import { createServer, startServer } from './env/node/authServer'; +import { createServer, startServer } from './authServer'; import { v4 as uuid } from 'uuid'; import { keychain } from './keychain'; diff --git a/extensions/microsoft-authentication/src/env/node/authServer.ts b/extensions/microsoft-authentication/src/authServer.ts similarity index 95% rename from extensions/microsoft-authentication/src/env/node/authServer.ts rename to extensions/microsoft-authentication/src/authServer.ts index 88e02015fd2..caae72ba52c 100644 --- a/extensions/microsoft-authentication/src/env/node/authServer.ts +++ b/extensions/microsoft-authentication/src/authServer.ts @@ -130,10 +130,10 @@ export function createServer(nonce: string) { } break; case '/': - sendFile(res, path.join(__dirname, '../../../media/auth.html'), 'text/html; charset=utf-8'); + sendFile(res, path.join(__dirname, '../media/auth.html'), 'text/html; charset=utf-8'); break; case '/auth.css': - sendFile(res, path.join(__dirname, '../../../media/auth.css'), 'text/css; charset=utf-8'); + sendFile(res, path.join(__dirname, '../media/auth.css'), 'text/css; charset=utf-8'); break; case '/callback': deferredCode.resolve(callback(nonce, reqUrl) diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 3548671829f..ab6a40c6b80 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -78,7 +78,7 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) { }; return merge(defaultConfig, extConfig); -}; +} function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { @@ -135,7 +135,7 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { }; return merge(defaultConfig, extConfig); -}; +} module.exports = withNodeDefaults; diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts new file mode 100644 index 00000000000..8c44b2d9575 --- /dev/null +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider'; +import { Api, getExtensionApi } from './api'; +import { registerCommands } from './commands/index'; +import { LanguageConfigurationManager } from './features/languageConfiguration'; +import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost'; +import { CommandManager } from './utils/commandManager'; +import { PluginManager } from './utils/plugins'; +import { noopRequestCancellerFactory } from './tsServer/cancellation'; + +export function activate( + context: vscode.ExtensionContext +): Api { + const pluginManager = new PluginManager(); + context.subscriptions.push(pluginManager); + + const commandManager = new CommandManager(); + context.subscriptions.push(commandManager); + + const onCompletionAccepted = new vscode.EventEmitter(); + context.subscriptions.push(onCompletionAccepted); + + const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, noopLogDirectoryProvider, noopRequestCancellerFactory, item => { + onCompletionAccepted.fire(item); + }); + + registerCommands(commandManager, lazyClientHost, pluginManager); + // context.subscriptions.push(task.register(lazyClientHost.map(x => x.serviceClient))); + context.subscriptions.push(new LanguageConfigurationManager()); + + import('./features/tsconfig').then(module => { + context.subscriptions.push(module.register()); + }); + + context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager)); + + return getExtensionApi(onCompletionAccepted.event, pluginManager); +} + diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index 657755e964a..85b79d3202c 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -5,20 +5,15 @@ import * as rimraf from 'rimraf'; import * as vscode from 'vscode'; +import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron'; import { Api, getExtensionApi } from './api'; import { registerCommands } from './commands/index'; import { LanguageConfigurationManager } from './features/languageConfiguration'; import * as task from './features/task'; -import TypeScriptServiceClientHost from './typeScriptServiceClientHost'; -import { flatten } from './utils/arrays'; +import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost'; +import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron'; import { CommandManager } from './utils/commandManager'; import * as electron from './utils/electron'; -import * as fileSchemes from './utils/fileSchemes'; -import { standardLanguageDescriptions } from './utils/languageDescription'; -import * as ProjectStatus from './utils/largeProjectStatus'; -import { lazy, Lazy } from './utils/lazy'; -import LogDirectoryProvider from './utils/logDirectoryProvider'; -import ManagedFileContextManager from './utils/managedFileContext'; import { PluginManager } from './utils/plugins'; export function activate( @@ -33,7 +28,9 @@ export function activate( const onCompletionAccepted = new vscode.EventEmitter(); context.subscriptions.push(onCompletionAccepted); - const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, item => { + const logDirectoryProvider = new NodeLogDirectoryProvider(context); + + const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, logDirectoryProvider, nodeRequestCancellerFactory, item => { onCompletionAccepted.fire(item); }); @@ -50,84 +47,6 @@ export function activate( return getExtensionApi(onCompletionAccepted.event, pluginManager); } -function createLazyClientHost( - context: vscode.ExtensionContext, - pluginManager: PluginManager, - commandManager: CommandManager, - onCompletionAccepted: (item: vscode.CompletionItem) => void, -): Lazy { - return lazy(() => { - const logDirectoryProvider = new LogDirectoryProvider(context); - - const clientHost = new TypeScriptServiceClientHost( - standardLanguageDescriptions, - context.workspaceState, - pluginManager, - commandManager, - logDirectoryProvider, - onCompletionAccepted); - - context.subscriptions.push(clientHost); - - clientHost.serviceClient.onReady(() => { - context.subscriptions.push( - ProjectStatus.create( - clientHost.serviceClient, - clientHost.serviceClient.telemetryReporter)); - }); - - return clientHost; - }); -} - -function lazilyActivateClient( - lazyClientHost: Lazy, - pluginManager: PluginManager, -) { - const disposables: vscode.Disposable[] = []; - - const supportedLanguage = flatten([ - ...standardLanguageDescriptions.map(x => x.modeIds), - ...pluginManager.plugins.map(x => x.languages) - ]); - - let hasActivated = false; - const maybeActivate = (textDocument: vscode.TextDocument): boolean => { - if (!hasActivated && isSupportedDocument(supportedLanguage, textDocument)) { - hasActivated = true; - // Force activation - void lazyClientHost.value; - - disposables.push(new ManagedFileContextManager(resource => { - return lazyClientHost.value.serviceClient.toPath(resource); - })); - return true; - } - return false; - }; - - const didActivate = vscode.workspace.textDocuments.some(maybeActivate); - if (!didActivate) { - const openListener = vscode.workspace.onDidOpenTextDocument(doc => { - if (maybeActivate(doc)) { - openListener.dispose(); - } - }, undefined, disposables); - } - - return vscode.Disposable.from(...disposables); -} - -function isSupportedDocument( - supportedLanguage: string[], - document: vscode.TextDocument -): boolean { - if (supportedLanguage.indexOf(document.languageId) < 0) { - return false; - } - return fileSchemes.isSupportedScheme(document.uri.scheme); -} - export function deactivate() { rimraf.sync(electron.getInstanceDir()); } diff --git a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts index 7fb00fbc894..fdc552a293c 100644 --- a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts @@ -68,11 +68,16 @@ type BufferOperation = CloseOperation | OpenOperation | ChangeOperation; */ class BufferSynchronizer { - private readonly _pending = new ResourceMap(); + private readonly _pending: ResourceMap; constructor( - private readonly client: ITypeScriptServiceClient - ) { } + private readonly client: ITypeScriptServiceClient, + onCaseInsenitiveFileSystem: boolean + ) { + this._pending = new ResourceMap(undefined, { + onCaseInsenitiveFileSystem + }); + } public open(resource: vscode.Uri, args: Proto.OpenRequestArgs) { if (this.supportsBatching) { @@ -275,7 +280,7 @@ class PendingDiagnostics extends ResourceMap { .sort((a, b) => a.value - b.value) .map(entry => entry.resource); - const map = new ResourceMap(); + const map = new ResourceMap(undefined, this.config); for (const resource of orderedResources) { map.set(resource, undefined); } @@ -347,7 +352,8 @@ export default class BufferSyncSupport extends Disposable { constructor( client: ITypeScriptServiceClient, - modeIds: readonly string[] + modeIds: readonly string[], + onCaseInsenitiveFileSystem: boolean ) { super(); this.client = client; @@ -356,9 +362,9 @@ export default class BufferSyncSupport extends Disposable { this.diagnosticDelayer = new Delayer(300); const pathNormalizer = (path: vscode.Uri) => this.client.normalizedPath(path); - this.syncedBuffers = new SyncedBufferMap(pathNormalizer); - this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer); - this.synchronizer = new BufferSynchronizer(client); + this.syncedBuffers = new SyncedBufferMap(pathNormalizer, { onCaseInsenitiveFileSystem }); + this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer, { onCaseInsenitiveFileSystem }); + this.synchronizer = new BufferSynchronizer(client, onCaseInsenitiveFileSystem); this.updateConfiguration(); vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this, this._disposables); diff --git a/extensions/typescript-language-features/src/features/callHierarchy.ts b/extensions/typescript-language-features/src/features/callHierarchy.ts index 1bc85e4ca11..85115b59e5f 100644 --- a/extensions/typescript-language-features/src/features/callHierarchy.ts +++ b/extensions/typescript-language-features/src/features/callHierarchy.ts @@ -7,9 +7,10 @@ import * as path from 'path'; import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import * as PConst from '../protocol.const'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; -import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import { parseKindModifier } from '../utils/modifiers'; import * as typeConverters from '../utils/typeConverters'; @@ -117,13 +118,14 @@ function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCa } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient ) { return conditionalRegistration([ requireMinVersion(client, TypeScriptCallHierarchySupport.minVersion), + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), ], () => { - return vscode.languages.registerCallHierarchyProvider(selector, + return vscode.languages.registerCallHierarchyProvider(selector.syntax, new TypeScriptCallHierarchySupport(client)); }); } diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 5ef98b8b07e..229192d9e08 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -7,12 +7,13 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; import * as PConst from '../protocol.const'; -import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; import API from '../utils/api'; import { nulToken } from '../utils/cancellation'; import { applyCodeAction } from '../utils/codeAction'; import { Command, CommandManager } from '../utils/commandManager'; -import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireConfiguration } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import { parseKindModifier } from '../utils/modifiers'; import * as Previewer from '../utils/previewer'; import { snippetForFunctionCall } from '../utils/snippetForFunctionCall'; @@ -795,7 +796,7 @@ function shouldExcludeCompletionEntry( } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, typingsStatus: TypingsStatus, @@ -806,8 +807,9 @@ export function register( ) { return conditionalRegistration([ requireConfiguration(modeId, 'suggest.enabled'), + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), ], () => { - return vscode.languages.registerCompletionItemProvider(selector, + return vscode.languages.registerCompletionItemProvider(selector.syntax, new TypeScriptCompletionItemProvider(client, modeId, typingsStatus, fileConfigurationManager, commandManager, telemetryReporter, onCompletionAccepted), ...TypeScriptCompletionItemProvider.triggerCharacters); }); diff --git a/extensions/typescript-language-features/src/features/definitions.ts b/extensions/typescript-language-features/src/features/definitions.ts index fd3a1f10e79..7c01400c206 100644 --- a/extensions/typescript-language-features/src/features/definitions.ts +++ b/extensions/typescript-language-features/src/features/definitions.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; import DefinitionProviderBase from './definitionProviderBase'; @@ -58,9 +60,13 @@ export default class TypeScriptDefinitionProvider extends DefinitionProviderBase } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return vscode.languages.registerDefinitionProvider(selector, - new TypeScriptDefinitionProvider(client)); + return conditionalRegistration([ + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerDefinitionProvider(selector.syntax, + new TypeScriptDefinitionProvider(client)); + }); } diff --git a/extensions/typescript-language-features/src/features/diagnostics.ts b/extensions/typescript-language-features/src/features/diagnostics.ts index ec0566d67cb..25a9591cc56 100644 --- a/extensions/typescript-language-features/src/features/diagnostics.ts +++ b/extensions/typescript-language-features/src/features/diagnostics.ts @@ -142,17 +142,21 @@ class DiagnosticSettings { } export class DiagnosticsManager extends Disposable { - private readonly _diagnostics = new ResourceMap(); + private readonly _diagnostics: ResourceMap; private readonly _settings = new DiagnosticSettings(); private readonly _currentDiagnostics: vscode.DiagnosticCollection; - private readonly _pendingUpdates = new ResourceMap(); + private readonly _pendingUpdates: ResourceMap; private readonly _updateDelay = 50; constructor( - owner: string + owner: string, + onCaseInsenitiveFileSystem: boolean ) { super(); + this._diagnostics = new ResourceMap(undefined, { onCaseInsenitiveFileSystem }); + this._pendingUpdates = new ResourceMap(undefined, { onCaseInsenitiveFileSystem }); + this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner)); } diff --git a/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts b/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts index cfa35571d1d..2b0f683e589 100644 --- a/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts +++ b/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; +import { DocumentSelector } from '../utils/documentSelector'; const localize = nls.loadMessageBundle(); @@ -80,10 +81,10 @@ class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvide } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return vscode.languages.registerCompletionItemProvider(selector, + return vscode.languages.registerCompletionItemProvider(selector.syntax, new DirectiveCommentCompletionProvider(client), '@'); } diff --git a/extensions/typescript-language-features/src/features/documentHighlight.ts b/extensions/typescript-language-features/src/features/documentHighlight.ts index 61477e06749..3a7b43ed5eb 100644 --- a/extensions/typescript-language-features/src/features/documentHighlight.ts +++ b/extensions/typescript-language-features/src/features/documentHighlight.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import { flatten } from '../utils/arrays'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; class TypeScriptDocumentHighlightProvider implements vscode.DocumentHighlightProvider { @@ -48,9 +49,9 @@ function convertDocumentHighlight(highlight: Proto.DocumentHighlightsItem): Read } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return vscode.languages.registerDocumentHighlightProvider(selector, + return vscode.languages.registerDocumentHighlightProvider(selector.syntax, new TypeScriptDocumentHighlightProvider(client)); } diff --git a/extensions/typescript-language-features/src/features/documentSymbol.ts b/extensions/typescript-language-features/src/features/documentSymbol.ts index 8b44581c6de..d0b8b9ad901 100644 --- a/extensions/typescript-language-features/src/features/documentSymbol.ts +++ b/extensions/typescript-language-features/src/features/documentSymbol.ts @@ -6,10 +6,11 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import * as PConst from '../protocol.const'; -import { ITypeScriptServiceClient } from '../typescriptService'; -import * as typeConverters from '../utils/typeConverters'; import { CachedResponse } from '../tsServer/cachedResponse'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import { DocumentSelector } from '../utils/documentSelector'; import { parseKindModifier } from '../utils/modifiers'; +import * as typeConverters from '../utils/typeConverters'; const getSymbolKind = (kind: string): vscode.SymbolKind => { switch (kind) { @@ -111,10 +112,10 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, cachedResponse: CachedResponse, ) { - return vscode.languages.registerDocumentSymbolProvider(selector, + return vscode.languages.registerDocumentSymbolProvider(selector.syntax, new TypeScriptDocumentSymbolProvider(client, cachedResponse), { label: 'TypeScript' }); } diff --git a/extensions/typescript-language-features/src/features/fileConfigurationManager.ts b/extensions/typescript-language-features/src/features/fileConfigurationManager.ts index 55d5324469b..61fcc493906 100644 --- a/extensions/typescript-language-features/src/features/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/features/fileConfigurationManager.ts @@ -9,6 +9,7 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; import { Disposable } from '../utils/dispose'; import * as fileSchemes from '../utils/fileSchemes'; +import { onCaseInsenitiveFileSystem } from '../utils/fileSystem'; import { isTypeScriptDocument } from '../utils/languageModeIds'; import { equals } from '../utils/objects'; import { ResourceMap } from '../utils/resourceMap'; @@ -30,12 +31,13 @@ function areFileConfigurationsEqual(a: FileConfiguration, b: FileConfiguration): } export default class FileConfigurationManager extends Disposable { - private readonly formatOptions = new ResourceMap>(); + private readonly formatOptions: ResourceMap>; public constructor( private readonly client: ITypeScriptServiceClient ) { super(); + this.formatOptions = new ResourceMap(undefined, { onCaseInsenitiveFileSystem: onCaseInsenitiveFileSystem() }); vscode.workspace.onDidCloseTextDocument(textDocument => { // When a document gets closed delete the cached formatting options. // This is necessary since the tsserver now closed a project when its diff --git a/extensions/typescript-language-features/src/features/fixAll.ts b/extensions/typescript-language-features/src/features/fixAll.ts index 422c26e4131..6a43c535a8b 100644 --- a/extensions/typescript-language-features/src/features/fixAll.ts +++ b/extensions/typescript-language-features/src/features/fixAll.ts @@ -6,9 +6,10 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; -import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as errorCodes from '../utils/errorCodes'; import * as fixNames from '../utils/fixNames'; import * as typeConverters from '../utils/typeConverters'; @@ -249,15 +250,16 @@ class TypeScriptAutoFixProvider implements vscode.CodeActionProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, diagnosticsManager: DiagnosticsManager, ) { return conditionalRegistration([ - requireMinVersion(client, API.v300) + requireMinVersion(client, API.v300), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { const provider = new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager); - return vscode.languages.registerCodeActionsProvider(selector, provider, provider.metadata); + return vscode.languages.registerCodeActionsProvider(selector.semantic, provider, provider.metadata); }); } diff --git a/extensions/typescript-language-features/src/features/folding.ts b/extensions/typescript-language-features/src/features/folding.ts index c61028cb6ef..5b18decb440 100644 --- a/extensions/typescript-language-features/src/features/folding.ts +++ b/extensions/typescript-language-features/src/features/folding.ts @@ -9,6 +9,7 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; import { coalesce } from '../utils/arrays'; import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider { @@ -73,13 +74,13 @@ class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ): vscode.Disposable { return conditionalRegistration([ requireMinVersion(client, TypeScriptFoldingProvider.minVersion), ], () => { - return vscode.languages.registerFoldingRangeProvider(selector, + return vscode.languages.registerFoldingRangeProvider(selector.syntax, new TypeScriptFoldingProvider(client)); }); } diff --git a/extensions/typescript-language-features/src/features/formatting.ts b/extensions/typescript-language-features/src/features/formatting.ts index 525567e0bb2..cc2469774a5 100644 --- a/extensions/typescript-language-features/src/features/formatting.ts +++ b/extensions/typescript-language-features/src/features/formatting.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; @@ -84,7 +85,7 @@ class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEdit } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager @@ -94,8 +95,8 @@ export function register( ], () => { const formattingProvider = new TypeScriptFormattingProvider(client, fileConfigurationManager); return vscode.Disposable.from( - vscode.languages.registerOnTypeFormattingEditProvider(selector, formattingProvider, ';', '}', '\n'), - vscode.languages.registerDocumentRangeFormattingEditProvider(selector, formattingProvider), + vscode.languages.registerOnTypeFormattingEditProvider(selector.syntax, formattingProvider, ';', '}', '\n'), + vscode.languages.registerDocumentRangeFormattingEditProvider(selector.syntax, formattingProvider), ); }); } diff --git a/extensions/typescript-language-features/src/features/hover.ts b/extensions/typescript-language-features/src/features/hover.ts index 2ff92d6e308..c6c4860f663 100644 --- a/extensions/typescript-language-features/src/features/hover.ts +++ b/extensions/typescript-language-features/src/features/hover.ts @@ -5,7 +5,9 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import { markdownDocumentation } from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; @@ -51,9 +53,13 @@ class TypeScriptHoverProvider implements vscode.HoverProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient ): vscode.Disposable { - return vscode.languages.registerHoverProvider(selector, - new TypeScriptHoverProvider(client)); + return conditionalRegistration([ + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerHoverProvider(selector.syntax, + new TypeScriptHoverProvider(client)); + }); } diff --git a/extensions/typescript-language-features/src/features/implementations.ts b/extensions/typescript-language-features/src/features/implementations.ts index c7cdeeb755f..bf3ddfee414 100644 --- a/extensions/typescript-language-features/src/features/implementations.ts +++ b/extensions/typescript-language-features/src/features/implementations.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import DefinitionProviderBase from './definitionProviderBase'; class TypeScriptImplementationProvider extends DefinitionProviderBase implements vscode.ImplementationProvider { @@ -14,9 +16,13 @@ class TypeScriptImplementationProvider extends DefinitionProviderBase implements } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return vscode.languages.registerImplementationProvider(selector, - new TypeScriptImplementationProvider(client)); + return conditionalRegistration([ + requireSomeCapability(client, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerImplementationProvider(selector.semantic, + new TypeScriptImplementationProvider(client)); + }); } diff --git a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts index 4e56d6499e7..0fc3c242400 100644 --- a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts @@ -9,7 +9,8 @@ import type * as Proto from '../protocol'; import * as PConst from '../protocol.const'; import { CachedResponse } from '../tsServer/cachedResponse'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; -import { conditionalRegistration, requireCapability, requireConfiguration } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireConfiguration } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; import { getSymbolRange, ReferencesCodeLens, TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider'; @@ -89,16 +90,16 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, cachedResponse: CachedResponse, ) { return conditionalRegistration([ requireConfiguration(modeId, 'implementationsCodeLens.enabled'), - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { - return vscode.languages.registerCodeLensProvider(selector, + return vscode.languages.registerCodeLensProvider(selector.semantic, new TypeScriptImplementationsCodeLensProvider(client, cachedResponse)); }); } diff --git a/extensions/typescript-language-features/src/features/jsDocCompletions.ts b/extensions/typescript-language-features/src/features/jsDocCompletions.ts index 9c930ac5ad7..6e091af1692 100644 --- a/extensions/typescript-language-features/src/features/jsDocCompletions.ts +++ b/extensions/typescript-language-features/src/features/jsDocCompletions.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { ITypeScriptServiceClient } from '../typescriptService'; import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; @@ -110,14 +111,14 @@ export function templateToSnippet(template: string): vscode.SnippetString { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, ): vscode.Disposable { return conditionalRegistration([ requireConfiguration(modeId, 'suggest.completeJSDocs') ], () => { - return vscode.languages.registerCompletionItemProvider(selector, + return vscode.languages.registerCompletionItemProvider(selector.syntax, new JsDocCompletionProvider(client), '*'); }); diff --git a/extensions/typescript-language-features/src/features/organizeImports.ts b/extensions/typescript-language-features/src/features/organizeImports.ts index 186d12e3f2e..80175554c9e 100644 --- a/extensions/typescript-language-features/src/features/organizeImports.ts +++ b/extensions/typescript-language-features/src/features/organizeImports.ts @@ -11,6 +11,7 @@ import API from '../utils/api'; import { nulToken } from '../utils/cancellation'; import { Command, CommandManager } from '../utils/commandManager'; import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import { TelemetryReporter } from '../utils/telemetry'; import * as typeconverts from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; @@ -99,7 +100,7 @@ export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvi } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, commandManager: CommandManager, fileConfigurationManager: FileConfigurationManager, @@ -109,7 +110,7 @@ export function register( requireMinVersion(client, OrganizeImportsCodeActionProvider.minVersion) ], () => { const organizeImportsProvider = new OrganizeImportsCodeActionProvider(client, commandManager, fileConfigurationManager, telemetryReporter); - return vscode.languages.registerCodeActionsProvider(selector, + return vscode.languages.registerCodeActionsProvider(selector.syntax, organizeImportsProvider, organizeImportsProvider.metadata); }); diff --git a/extensions/typescript-language-features/src/features/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 1d6fd6aa349..b2c2e031df2 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -18,7 +18,8 @@ import * as typeConverters from '../utils/typeConverters'; import { DiagnosticsManager } from './diagnostics'; import FileConfigurationManager from './fileConfigurationManager'; import { equals } from '../utils/objects'; -import { conditionalRegistration, requireCapability } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; const localize = nls.loadMessageBundle(); @@ -403,7 +404,7 @@ function isPreferredFix( } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, commandManager: CommandManager, @@ -411,9 +412,9 @@ export function register( telemetryReporter: TelemetryReporter ) { return conditionalRegistration([ - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { - return vscode.languages.registerCodeActionsProvider(selector, + return vscode.languages.registerCodeActionsProvider(selector.semantic, new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter), TypeScriptQuickFixProvider.metadata); }); diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index a56267acb42..4b5612e41e7 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -11,7 +11,8 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService import API from '../utils/api'; import { nulToken } from '../utils/cancellation'; import { Command, CommandManager } from '../utils/commandManager'; -import { conditionalRegistration, requireCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as fileSchemes from '../utils/fileSchemes'; import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; @@ -396,7 +397,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, formattingOptionsManager: FormattingOptionsManager, commandManager: CommandManager, @@ -404,9 +405,9 @@ export function register( ) { return conditionalRegistration([ requireMinVersion(client, TypeScriptRefactorProvider.minVersion), - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { - return vscode.languages.registerCodeActionsProvider(selector, + return vscode.languages.registerCodeActionsProvider(selector.semantic, new TypeScriptRefactorProvider(client, formattingOptionsManager, commandManager, telemetryReporter), TypeScriptRefactorProvider.metadata); }); diff --git a/extensions/typescript-language-features/src/features/references.ts b/extensions/typescript-language-features/src/features/references.ts index d77ecc5b10b..1ee8d150429 100644 --- a/extensions/typescript-language-features/src/features/references.ts +++ b/extensions/typescript-language-features/src/features/references.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; class TypeScriptReferenceSupport implements vscode.ReferenceProvider { @@ -42,9 +44,13 @@ class TypeScriptReferenceSupport implements vscode.ReferenceProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient ) { - return vscode.languages.registerReferenceProvider(selector, - new TypeScriptReferenceSupport(client)); + return conditionalRegistration([ + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerReferenceProvider(selector.syntax, + new TypeScriptReferenceSupport(client)); + }); } diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index bfda4f07230..0666ba4a3d5 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -8,8 +8,10 @@ import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; import * as PConst from '../protocol.const'; import { CachedResponse } from '../tsServer/cachedResponse'; -import { ITypeScriptServiceClient, ClientCapability } from '../typescriptService'; -import { conditionalRegistration, requireConfiguration, requireCapability } from '../utils/dependentRegistration'; +import { ExectuionTarget } from '../tsServer/server'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireConfiguration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; import { getSymbolRange, ReferencesCodeLens, TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider'; @@ -27,7 +29,11 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise { const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('references', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); + const response = await this.client.execute('references', args, token, { + lowPriority: true, + executionTarget: ExectuionTarget.Semantic, + cancelOnResourceChange: codeLens.document, + }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand @@ -122,16 +128,16 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, cachedResponse: CachedResponse, ) { return conditionalRegistration([ requireConfiguration(modeId, 'referencesCodeLens.enabled'), - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { - return vscode.languages.registerCodeLensProvider(selector, + return vscode.languages.registerCodeLensProvider(selector.semantic, new TypeScriptReferencesCodeLensProvider(client, cachedResponse, modeId)); }); } diff --git a/extensions/typescript-language-features/src/features/rename.ts b/extensions/typescript-language-features/src/features/rename.ts index da784760d66..e3e1b3c694e 100644 --- a/extensions/typescript-language-features/src/features/rename.ts +++ b/extensions/typescript-language-features/src/features/rename.ts @@ -9,7 +9,8 @@ import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; import { ClientCapability, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; import API from '../utils/api'; -import { conditionalRegistration, requireCapability } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; @@ -138,14 +139,14 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, ) { return conditionalRegistration([ - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { - return vscode.languages.registerRenameProvider(selector, + return vscode.languages.registerRenameProvider(selector.semantic, new TypeScriptRenameProvider(client, fileConfigurationManager)); }); } diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index 6174b51c8e5..c404ac0b95a 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -9,7 +9,8 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; import { ClientCapability, ExecConfig, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; import API from '../utils/api'; -import { conditionalRegistration, requireCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; const minTypeScriptVersion = API.fromVersionString(`${VersionRequirement.major}.${VersionRequirement.minor}`); @@ -17,15 +18,18 @@ const minTypeScriptVersion = API.fromVersionString(`${VersionRequirement.major}. // as we don't do deltas, for performance reasons, don't compute semantic tokens for documents above that limit const CONTENT_LENGTH_LIMIT = 100000; -export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { +export function register( + selector: DocumentSelector, + client: ITypeScriptServiceClient, +) { return conditionalRegistration([ requireMinVersion(client, minTypeScriptVersion), - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { const provider = new DocumentSemanticTokensProvider(client); return vscode.Disposable.from( // register only as a range provider - vscode.languages.registerDocumentRangeSemanticTokensProvider(selector, provider, provider.getLegend()), + vscode.languages.registerDocumentRangeSemanticTokensProvider(selector.semantic, provider, provider.getLegend()), ); }); } diff --git a/extensions/typescript-language-features/src/features/signatureHelp.ts b/extensions/typescript-language-features/src/features/signatureHelp.ts index 8e751f8c114..93901b9bb25 100644 --- a/extensions/typescript-language-features/src/features/signatureHelp.ts +++ b/extensions/typescript-language-features/src/features/signatureHelp.ts @@ -5,7 +5,9 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as Previewer from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; @@ -120,12 +122,16 @@ function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.Signatur } } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return vscode.languages.registerSignatureHelpProvider(selector, - new TypeScriptSignatureHelpProvider(client), { - triggerCharacters: TypeScriptSignatureHelpProvider.triggerCharacters, - retriggerCharacters: TypeScriptSignatureHelpProvider.retriggerCharacters + return conditionalRegistration([ + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerSignatureHelpProvider(selector.syntax, + new TypeScriptSignatureHelpProvider(client), { + triggerCharacters: TypeScriptSignatureHelpProvider.triggerCharacters, + retriggerCharacters: TypeScriptSignatureHelpProvider.retriggerCharacters + }); }); } diff --git a/extensions/typescript-language-features/src/features/smartSelect.ts b/extensions/typescript-language-features/src/features/smartSelect.ts index e61c168bd8c..f769347f15c 100644 --- a/extensions/typescript-language-features/src/features/smartSelect.ts +++ b/extensions/typescript-language-features/src/features/smartSelect.ts @@ -8,6 +8,7 @@ import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; class SmartSelection implements vscode.SelectionRangeProvider { @@ -49,12 +50,12 @@ class SmartSelection implements vscode.SelectionRangeProvider { } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { return conditionalRegistration([ requireMinVersion(client, SmartSelection.minVersion), ], () => { - return vscode.languages.registerSelectionRangeProvider(selector, new SmartSelection(client)); + return vscode.languages.registerSelectionRangeProvider(selector.syntax, new SmartSelection(client)); }); } diff --git a/extensions/typescript-language-features/src/features/tagClosing.ts b/extensions/typescript-language-features/src/features/tagClosing.ts index 40fee040f3e..289ce73b293 100644 --- a/extensions/typescript-language-features/src/features/tagClosing.ts +++ b/extensions/typescript-language-features/src/features/tagClosing.ts @@ -9,6 +9,7 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; import { conditionalRegistration, requireMinVersion, requireConfiguration, Condition } from '../utils/dependentRegistration'; import { Disposable } from '../utils/dispose'; +import { DocumentSelector } from '../utils/documentSelector'; import * as typeConverters from '../utils/typeConverters'; class TagClosing extends Disposable { @@ -151,13 +152,13 @@ function requireActiveDocument( } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, modeId: string, client: ITypeScriptServiceClient, ) { return conditionalRegistration([ requireMinVersion(client, TagClosing.minVersion), requireConfiguration(modeId, 'autoClosingTags'), - requireActiveDocument(selector) + requireActiveDocument(selector.syntax) ], () => new TagClosing(client)); } diff --git a/extensions/typescript-language-features/src/features/typeDefinitions.ts b/extensions/typescript-language-features/src/features/typeDefinitions.ts index 6f63e44df0a..4aef4f41ed4 100644 --- a/extensions/typescript-language-features/src/features/typeDefinitions.ts +++ b/extensions/typescript-language-features/src/features/typeDefinitions.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ITypeScriptServiceClient } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; import DefinitionProviderBase from './definitionProviderBase'; export default class TypeScriptTypeDefinitionProvider extends DefinitionProviderBase implements vscode.TypeDefinitionProvider { @@ -14,9 +16,13 @@ export default class TypeScriptTypeDefinitionProvider extends DefinitionProvider } export function register( - selector: vscode.DocumentSelector, + selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return vscode.languages.registerTypeDefinitionProvider(selector, - new TypeScriptTypeDefinitionProvider(client)); + return conditionalRegistration([ + requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerTypeDefinitionProvider(selector.syntax, + new TypeScriptTypeDefinitionProvider(client)); + }); } diff --git a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts index 84171e18fb7..2c6354c01a8 100644 --- a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts @@ -11,7 +11,7 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService import API from '../utils/api'; import { Delayer } from '../utils/async'; import { nulToken } from '../utils/cancellation'; -import { conditionalRegistration, requireCapability, requireMinVersion } from '../utils/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability, requireMinVersion } from '../utils/dependentRegistration'; import { Disposable } from '../utils/dispose'; import * as fileSchemes from '../utils/fileSchemes'; import { doesResourceLookLikeATypeScriptFile } from '../utils/languageDescription'; @@ -296,7 +296,7 @@ export function register( ) { return conditionalRegistration([ requireMinVersion(client, UpdateImportsOnFileRenameHandler.minVersion), - requireCapability(client, ClientCapability.Semantic), + requireSomeCapability(client, ClientCapability.Semantic), ], () => { return new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles); }); diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index ee040667b68..c045bd53ae4 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -5,15 +5,15 @@ import { basename } from 'path'; import * as vscode from 'vscode'; -import { CachedResponse } from './tsServer/cachedResponse'; import { DiagnosticKind } from './features/diagnostics'; import FileConfigurationManager from './features/fileConfigurationManager'; +import { CachedResponse } from './tsServer/cachedResponse'; import TypeScriptServiceClient from './typescriptServiceClient'; import { CommandManager } from './utils/commandManager'; import { Disposable } from './utils/dispose'; +import { DocumentSelector } from './utils/documentSelector'; import * as fileSchemes from './utils/fileSchemes'; import { LanguageDescription } from './utils/languageDescription'; -import { memoize } from './utils/memoize'; import { TelemetryReporter } from './utils/telemetry'; import TypingsStatus from './utils/typingsStatus'; @@ -39,15 +39,17 @@ export default class LanguageProvider extends Disposable { client.onReady(() => this.registerProviders()); } - @memoize - private get documentSelector(): vscode.DocumentFilter[] { - const documentSelector = []; + private get documentSelector(): DocumentSelector { + const semantic: vscode.DocumentFilter[] = []; + const syntax: vscode.DocumentFilter[] = []; for (const language of this.description.modeIds) { - for (const scheme of fileSchemes.supportedSchemes) { - documentSelector.push({ language, scheme }); + syntax.push({ language }); + for (const scheme of fileSchemes.semanticSupportedSchemes) { + semantic.push({ language, scheme }); } } - return documentSelector; + + return { semantic, syntax }; } private async registerProviders(): Promise { diff --git a/extensions/typescript-language-features/src/lazyClientHost.ts b/extensions/typescript-language-features/src/lazyClientHost.ts new file mode 100644 index 00000000000..4dc11db4ad8 --- /dev/null +++ b/extensions/typescript-language-features/src/lazyClientHost.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { OngoingRequestCancellerFactory } from './tsServer/cancellation'; +import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider'; +import TypeScriptServiceClientHost from './typeScriptServiceClientHost'; +import { flatten } from './utils/arrays'; +import { CommandManager } from './utils/commandManager'; +import { standardLanguageDescriptions } from './utils/languageDescription'; +import * as ProjectStatus from './utils/largeProjectStatus'; +import { lazy, Lazy } from './utils/lazy'; +import ManagedFileContextManager from './utils/managedFileContext'; +import { PluginManager } from './utils/plugins'; + +export function createLazyClientHost( + context: vscode.ExtensionContext, + pluginManager: PluginManager, + commandManager: CommandManager, + logDirectoryProvider: ILogDirectoryProvider, + cancellerFactory: OngoingRequestCancellerFactory, + onCompletionAccepted: (item: vscode.CompletionItem) => void, +): Lazy { + return lazy(() => { + const clientHost = new TypeScriptServiceClientHost( + standardLanguageDescriptions, + context.workspaceState, + pluginManager, + commandManager, + logDirectoryProvider, + cancellerFactory, + onCompletionAccepted); + + context.subscriptions.push(clientHost); + + clientHost.serviceClient.onReady(() => { + context.subscriptions.push( + ProjectStatus.create( + clientHost.serviceClient, + clientHost.serviceClient.telemetryReporter)); + }); + + return clientHost; + }); +} + + +export function lazilyActivateClient( + lazyClientHost: Lazy, + pluginManager: PluginManager, +) { + const disposables: vscode.Disposable[] = []; + + const supportedLanguage = flatten([ + ...standardLanguageDescriptions.map(x => x.modeIds), + ...pluginManager.plugins.map(x => x.languages) + ]); + + let hasActivated = false; + const maybeActivate = (textDocument: vscode.TextDocument): boolean => { + if (!hasActivated && isSupportedDocument(supportedLanguage, textDocument)) { + hasActivated = true; + // Force activation + void lazyClientHost.value; + + disposables.push(new ManagedFileContextManager(resource => { + return lazyClientHost.value.serviceClient.toPath(resource); + })); + return true; + } + return false; + }; + + const didActivate = vscode.workspace.textDocuments.some(maybeActivate); + if (!didActivate) { + const openListener = vscode.workspace.onDidOpenTextDocument(doc => { + if (maybeActivate(doc)) { + openListener.dispose(); + } + }, undefined, disposables); + } + + return vscode.Disposable.from(...disposables); +} + +function isSupportedDocument( + supportedLanguage: readonly string[], + document: vscode.TextDocument +): boolean { + return supportedLanguage.indexOf(document.languageId) >= 0; +} diff --git a/extensions/typescript-language-features/src/test/server.test.ts b/extensions/typescript-language-features/src/test/server.test.ts index d7189bdfb49..c225fc3c859 100644 --- a/extensions/typescript-language-features/src/test/server.test.ts +++ b/extensions/typescript-language-features/src/test/server.test.ts @@ -6,12 +6,13 @@ import * as assert from 'assert'; import 'mocha'; import * as stream from 'stream'; -import { PipeRequestCanceller, TsServerProcess, ProcessBasedTsServer } from '../tsServer/server'; +import type * as Proto from '../protocol'; +import { NodeRequestCanceller } from '../tsServer/cancellation.electron'; +import { ProcessBasedTsServer, TsServerProcess } from '../tsServer/server'; import { nulToken } from '../utils/cancellation'; import Logger from '../utils/logger'; import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; -import type * as Proto from '../protocol'; const NoopTelemetryReporter = new class implements TelemetryReporter { @@ -63,7 +64,7 @@ suite('Server', () => { test('should send requests with increasing sequence numbers', async () => { const process = new FakeServerProcess(); - const server = new ProcessBasedTsServer('semantic', process, undefined, new PipeRequestCanceller('semantic', undefined, tracer), undefined!, NoopTelemetryReporter, tracer); + const server = new ProcessBasedTsServer('semantic', process, undefined, new NodeRequestCanceller('semantic', tracer), undefined!, NoopTelemetryReporter, tracer); const onWrite1 = process.onWrite(); server.executeImpl('geterr', {}, { isAsync: false, token: nulToken, expectsResult: true }); diff --git a/extensions/typescript-language-features/src/tsServer/cancellation.electron.ts b/extensions/typescript-language-features/src/tsServer/cancellation.electron.ts new file mode 100644 index 00000000000..e098889b450 --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/cancellation.electron.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import { OngoingRequestCanceller, OngoingRequestCancellerFactory } from './cancellation'; +import { getTempFile } from '../utils/electron'; +import Tracer from '../utils/tracer'; + +export class NodeRequestCanceller implements OngoingRequestCanceller { + public readonly cancellationPipeName: string; + + public constructor( + private readonly _serverId: string, + private readonly _tracer: Tracer, + ) { + this.cancellationPipeName = getTempFile('tscancellation'); + } + + public tryCancelOngoingRequest(seq: number): boolean { + if (!this.cancellationPipeName) { + return false; + } + this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); + try { + fs.writeFileSync(this.cancellationPipeName + seq, ''); + } catch { + // noop + } + return true; + } +} + + +export const nodeRequestCancellerFactory = new class implements OngoingRequestCancellerFactory { + create(serverId: string, tracer: Tracer): OngoingRequestCanceller { + return new NodeRequestCanceller(serverId, tracer); + } +}; diff --git a/extensions/typescript-language-features/src/tsServer/cancellation.ts b/extensions/typescript-language-features/src/tsServer/cancellation.ts new file mode 100644 index 00000000000..0eda4e574dc --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/cancellation.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Tracer from '../utils/tracer'; + +export interface OngoingRequestCanceller { + readonly cancellationPipeName: string | undefined; + tryCancelOngoingRequest(seq: number): boolean; +} + +export interface OngoingRequestCancellerFactory { + create(serverId: string, tracer: Tracer): OngoingRequestCanceller; +} + +const noopRequestCanceller = new class implements OngoingRequestCanceller { + public readonly cancellationPipeName = undefined; + + public tryCancelOngoingRequest(_seq: number): boolean { + return false; + } +}; + +export const noopRequestCancellerFactory = new class implements OngoingRequestCancellerFactory { + create(_serverId: string, _tracer: Tracer): OngoingRequestCanceller { + return noopRequestCanceller; + } +}; diff --git a/extensions/typescript-language-features/src/utils/logDirectoryProvider.ts b/extensions/typescript-language-features/src/tsServer/logDirectoryProvider.electron.ts similarity index 84% rename from extensions/typescript-language-features/src/utils/logDirectoryProvider.ts rename to extensions/typescript-language-features/src/tsServer/logDirectoryProvider.electron.ts index af6886e7043..60b6965b5a7 100644 --- a/extensions/typescript-language-features/src/utils/logDirectoryProvider.ts +++ b/extensions/typescript-language-features/src/tsServer/logDirectoryProvider.electron.ts @@ -6,9 +6,10 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { memoize } from './memoize'; +import { ILogDirectoryProvider } from './logDirectoryProvider'; +import { memoize } from '../utils/memoize'; -export default class LogDirectoryProvider { +export class NodeLogDirectoryProvider implements ILogDirectoryProvider { public constructor( private readonly context: vscode.ExtensionContext ) { } diff --git a/extensions/typescript-language-features/src/tsServer/logDirectoryProvider.ts b/extensions/typescript-language-features/src/tsServer/logDirectoryProvider.ts new file mode 100644 index 00000000000..75ef2316309 --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/logDirectoryProvider.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface ILogDirectoryProvider { + getNewLogDirectory(): string | undefined; +} + +export const noopLogDirectoryProvider = new class implements ILogDirectoryProvider { + public getNewLogDirectory(): undefined { + return undefined; + } +}; diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 55a14642a1f..6880474d69a 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { EventName } from '../protocol.const'; import { CallbackMap } from '../tsServer/callbackMap'; +import { OngoingRequestCanceller } from './cancellation'; import { RequestItem, RequestQueue, RequestQueueingType } from '../tsServer/requestQueue'; import { TypeScriptServerError } from '../tsServer/serverError'; import { ServerResponse, TypeScriptRequests } from '../typescriptService'; @@ -16,29 +16,9 @@ import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion } from '../utils/versionProvider'; -export interface OngoingRequestCanceller { - tryCancelOngoingRequest(seq: number): boolean; -} - -export class PipeRequestCanceller implements OngoingRequestCanceller { - public constructor( - private readonly _serverId: string, - private readonly _cancellationPipeName: string | undefined, - private readonly _tracer: Tracer, - ) { } - - public tryCancelOngoingRequest(seq: number): boolean { - if (!this._cancellationPipeName) { - return false; - } - this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); - try { - fs.writeFileSync(this._cancellationPipeName + seq, ''); - } catch { - // noop - } - return true; - } +export enum ExectuionTarget { + Semantic, + Syntax } export interface ITypeScriptServer { @@ -50,9 +30,9 @@ export interface ITypeScriptServer { kill(): void; - executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined; + executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean, executionTarget?: ExectuionTarget }): undefined; + executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise>; + executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise> | undefined; dispose(): void; } @@ -196,9 +176,9 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe } } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean, executionTarget?: ExectuionTarget }): undefined; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise>; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise> | undefined { const request = this._requestQueue.createRequest(command, args); const requestInfo: RequestItem = { request, @@ -296,6 +276,14 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe } +interface ExecuteInfo { + readonly isAsync: boolean; + readonly token?: vscode.CancellationToken; + readonly expectsResult: boolean; + readonly lowPriority?: boolean; + readonly executionTarget?: ExectuionTarget; +} + class RequestRouter { private static readonly sharedCommands = new Set([ @@ -308,13 +296,16 @@ class RequestRouter { ]); constructor( - private readonly servers: ReadonlyArray<{ readonly server: ITypeScriptServer, canRun?(command: keyof TypeScriptRequests): void }>, + private readonly servers: ReadonlyArray<{ + readonly server: ITypeScriptServer; + canRun?(command: keyof TypeScriptRequests, executeInfo: ExecuteInfo): void; + }>, private readonly delegate: TsServerDelegate, ) { } - public execute(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { - if (RequestRouter.sharedCommands.has(command)) { - // Dispatch shared commands to all server but only return from first one one + public execute(command: keyof TypeScriptRequests, args: any, executeInfo: ExecuteInfo): Promise> | undefined { + if (RequestRouter.sharedCommands.has(command) && typeof executeInfo.executionTarget === 'undefined') { + // Dispatch shared commands to all servers but only return from first one const requestStates: RequestState.State[] = this.servers.map(() => RequestState.Unresolved); @@ -368,7 +359,7 @@ class RequestRouter { } for (const { canRun, server } of this.servers) { - if (!canRun || canRun(command)) { + if (!canRun || canRun(command, executeInfo)) { return server.executeImpl(command, args, executeInfo); } } @@ -444,9 +435,9 @@ export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServ this.mainServer.kill(); } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean, executionTarget?: ExectuionTarget }): undefined; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise>; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise> | undefined { return this.router.execute(command, args, executeInfo); } } @@ -514,7 +505,12 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ [ { server: this.syntaxServer, - canRun: (command) => { + canRun: (command, execInfo) => { + switch (execInfo.executionTarget) { + case ExectuionTarget.Semantic: return false; + case ExectuionTarget.Syntax: return true; + } + if (SyntaxRoutingTsServer.syntaxAlwaysCommands.has(command)) { return true; } @@ -580,9 +576,9 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ this.semanticServer.kill(); } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean, executionTarget?: ExectuionTarget }): undefined; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise>; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, executionTarget?: ExectuionTarget }): Promise> | undefined { return this.router.execute(command, args, executeInfo); } } diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 5bc505d0bf6..f0fb5eb0752 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -5,11 +5,12 @@ import * as path from 'path'; import * as vscode from 'vscode'; +import { OngoingRequestCancellerFactory } from '../tsServer/cancellation'; import { ClientCapabilities, ClientCapability } from '../typescriptService'; import API from '../utils/api'; import { SeparateSyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration } from '../utils/configuration'; import * as electron from '../utils/electron'; -import LogDirectoryProvider from '../utils/logDirectoryProvider'; +import { ILogDirectoryProvider } from './logDirectoryProvider'; import Logger from '../utils/logger'; import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider'; import { PluginManager } from '../utils/plugins'; @@ -17,7 +18,7 @@ import { ChildServerProcess } from '../utils/serverProcess'; import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider'; -import { GetErrRoutingTsServer, ITypeScriptServer, PipeRequestCanceller, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate } from './server'; +import { GetErrRoutingTsServer, ITypeScriptServer, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate } from './server'; const enum ServerKind { Main = 'main', @@ -43,7 +44,7 @@ const enum CompositeServerType { export class TypeScriptServerSpawner { public constructor( private readonly _versionProvider: TypeScriptVersionProvider, - private readonly _logDirectoryProvider: LogDirectoryProvider, + private readonly _logDirectoryProvider: ILogDirectoryProvider, private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider, private readonly _logger: Logger, private readonly _telemetryReporter: TelemetryReporter, @@ -55,6 +56,7 @@ export class TypeScriptServerSpawner { capabilities: ClientCapabilities, configuration: TypeScriptServiceConfiguration, pluginManager: PluginManager, + cancellerFactory: OngoingRequestCancellerFactory, delegate: TsServerDelegate, ): ITypeScriptServer { let primaryServer: ITypeScriptServer; @@ -65,26 +67,26 @@ export class TypeScriptServerSpawner { { const enableDynamicRouting = serverType === CompositeServerType.DynamicSeparateSyntax; primaryServer = new SyntaxRoutingTsServer({ - syntax: this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager), - semantic: this.spawnTsServer(ServerKind.Semantic, version, configuration, pluginManager) + syntax: this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager, cancellerFactory), + semantic: this.spawnTsServer(ServerKind.Semantic, version, configuration, pluginManager, cancellerFactory), }, delegate, enableDynamicRouting); break; } case CompositeServerType.Single: { - primaryServer = this.spawnTsServer(ServerKind.Main, version, configuration, pluginManager); + primaryServer = this.spawnTsServer(ServerKind.Main, version, configuration, pluginManager, cancellerFactory); break; } case CompositeServerType.SyntaxOnly: { - primaryServer = this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager); + primaryServer = this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager, cancellerFactory); break; } } if (this.shouldUseSeparateDiagnosticsServer(configuration)) { return new GetErrRoutingTsServer({ - getErr: this.spawnTsServer(ServerKind.Diagnostics, version, configuration, pluginManager), + getErr: this.spawnTsServer(ServerKind.Diagnostics, version, configuration, pluginManager, cancellerFactory), primary: primaryServer, }, delegate); } @@ -126,10 +128,12 @@ export class TypeScriptServerSpawner { version: TypeScriptVersion, configuration: TypeScriptServiceConfiguration, pluginManager: PluginManager, + cancellerFactory: OngoingRequestCancellerFactory, ): ITypeScriptServer { const apiVersion = version.apiVersion || API.defaultVersion; - const { args, cancellationPipeName, tsServerLogFile } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager); + const canceller = cancellerFactory.create(kind, this._tracer); + const { args, tsServerLogFile } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName); if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) { if (tsServerLogFile) { @@ -147,7 +151,7 @@ export class TypeScriptServerSpawner { kind, new ChildServerProcess(childProcess), tsServerLogFile, - new PipeRequestCanceller(kind, cancellationPipeName, this._tracer), + canceller, version, this._telemetryReporter, this._tracer); @@ -171,7 +175,8 @@ export class TypeScriptServerSpawner { currentVersion: TypeScriptVersion, apiVersion: API, pluginManager: PluginManager, - ): { args: string[], cancellationPipeName: string, tsServerLogFile: string | undefined } { + cancellationPipeName: string | undefined, + ): { args: string[], tsServerLogFile: string | undefined } { const args: string[] = []; let tsServerLogFile: string | undefined; @@ -193,8 +198,9 @@ export class TypeScriptServerSpawner { args.push('--enableTelemetry'); } - const cancellationPipeName = electron.getTempFile('tscancellation'); - args.push('--cancellationPipeName', cancellationPipeName + '*'); + if (cancellationPipeName) { + args.push('--cancellationPipeName', cancellationPipeName + '*'); + } if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) { const logDir = this._logDirectoryProvider.getNewLogDirectory(); @@ -238,7 +244,7 @@ export class TypeScriptServerSpawner { args.push('--validateDefaultNpmLocation'); } - return { args, cancellationPipeName, tsServerLogFile }; + return { args, tsServerLogFile }; } private static getDebugPort(kind: ServerKind): number | undefined { diff --git a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts index ff06c82b96c..71a746b1f11 100644 --- a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts +++ b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts @@ -14,13 +14,14 @@ import FileConfigurationManager from './features/fileConfigurationManager'; import LanguageProvider from './languageProvider'; import * as Proto from './protocol'; import * as PConst from './protocol.const'; +import { OngoingRequestCancellerFactory } from './tsServer/cancellation'; import TypeScriptServiceClient from './typescriptServiceClient'; import { coalesce, flatten } from './utils/arrays'; import { CommandManager } from './utils/commandManager'; import { Disposable } from './utils/dispose'; import * as errorCodes from './utils/errorCodes'; import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription'; -import LogDirectoryProvider from './utils/logDirectoryProvider'; +import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider'; import { PluginManager } from './utils/plugins'; import * as typeConverters from './utils/typeConverters'; import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'; @@ -60,7 +61,8 @@ export default class TypeScriptServiceClientHost extends Disposable { workspaceState: vscode.Memento, pluginManager: PluginManager, private readonly commandManager: CommandManager, - logDirectoryProvider: LogDirectoryProvider, + logDirectoryProvider: ILogDirectoryProvider, + cancellerFactory: OngoingRequestCancellerFactory, onCompletionAccepted: (item: vscode.CompletionItem) => void, ) { super(); @@ -70,6 +72,7 @@ export default class TypeScriptServiceClientHost extends Disposable { workspaceState, pluginManager, logDirectoryProvider, + cancellerFactory, allModeIds)); this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => { diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 2c6ddefe9f3..dd6bf911f45 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import BufferSyncSupport from './features/bufferSyncSupport'; import * as Proto from './protocol'; +import { ExectuionTarget } from './tsServer/server'; import API from './utils/api'; import { TypeScriptServiceConfiguration } from './utils/configuration'; import { PluginManager } from './utils/plugins'; @@ -82,11 +83,24 @@ export type TypeScriptRequests = StandardTsServerRequests & NoResponseTsServerRe export type ExecConfig = { readonly lowPriority?: boolean; readonly nonRecoverable?: boolean; - readonly cancelOnResourceChange?: vscode.Uri + readonly cancelOnResourceChange?: vscode.Uri; + readonly executionTarget?: ExectuionTarget; }; export enum ClientCapability { + /** + * Basic syntax server. All clients should support this. + */ Syntax, + + /** + * Advanced syntax server that can provide single file IntelliSense. + */ + EnhancedSyntax, + + /** + * Complete, multi-file semantic server + */ Semantic, } @@ -138,7 +152,7 @@ export interface ITypeScriptServiceClient { readonly onTypesInstallerInitializationFailed: vscode.Event; readonly capabilities: ClientCapabilities; - readonly onDidChangeCapabilities: vscode.Event; + readonly onDidChangeCapabilities: vscode.Event; onReady(f: () => void): Promise; diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 0e9dd740636..2cfa3639801 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -7,6 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; +import { OngoingRequestCancellerFactory } from './tsServer/cancellation'; import BufferSyncSupport from './features/bufferSyncSupport'; import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics'; import * as Proto from './protocol'; @@ -19,7 +20,8 @@ import API from './utils/api'; import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'; import { Disposable } from './utils/dispose'; import * as fileSchemes from './utils/fileSchemes'; -import LogDirectoryProvider from './utils/logDirectoryProvider'; +import { onCaseInsenitiveFileSystem } from './utils/fileSystem'; +import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider'; import Logger from './utils/logger'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; import { PluginManager } from './utils/plugins'; @@ -92,7 +94,6 @@ namespace ServerState { } export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { - private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`; private readonly pathSeparator: string; private readonly inMemoryResourcePrefix = '^'; @@ -123,7 +124,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType constructor( private readonly workspaceState: vscode.Memento, public readonly pluginManager: PluginManager, - private readonly logDirectoryProvider: LogDirectoryProvider, + private readonly logDirectoryProvider: ILogDirectoryProvider, + private readonly cancellerFactory: OngoingRequestCancellerFactory, allModeIds: readonly string[] ) { super(); @@ -148,10 +150,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.restartTsServer(); })); - this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds); + this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem()); this.onReady(() => { this.bufferSyncSupport.listen(); }); - this.diagnosticsManager = new DiagnosticsManager('typescript'); + this.diagnosticsManager = new DiagnosticsManager('typescript', onCaseInsenitiveFileSystem()); this.bufferSyncSupport.onDelete(resource => { this.cancelInflightRequestsForResource(resource); this.diagnosticsManager.delete(resource); @@ -204,13 +206,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public get capabilities() { + if (this.apiVersion.gte(API.v400)) { + return new ClientCapabilities( + ClientCapability.Syntax, + ClientCapability.EnhancedSyntax, + ClientCapability.Semantic); + } return new ClientCapabilities( - ClientCapability.Semantic, ClientCapability.Syntax, - ); + ClientCapability.Semantic); } - private readonly _onDidChangeCapabilities = this._register(new vscode.EventEmitter()); + private readonly _onDidChangeCapabilities = this._register(new vscode.EventEmitter()); readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event; private cancelInflightRequestsForResource(resource: vscode.Uri): void { @@ -347,7 +354,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType const apiVersion = version.apiVersion || API.defaultVersion; let mytoken = ++this.token; - const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, { + const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, this.cancellerFactory, { onFatalError: (command, err) => this.fatalError(command, err), }); this.serverState = new ServerState.Running(handle, apiVersion, undefined, true); @@ -434,7 +441,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this._onReady!.resolve(); this._onTsServerStarted.fire({ version: version, usedApiVersion: apiVersion }); - + this._onDidChangeCapabilities.fire(); return this.serverState; } @@ -605,27 +612,23 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public normalizedPath(resource: vscode.Uri): string | undefined { - if (resource.scheme === fileSchemes.walkThroughSnippet || resource.scheme === fileSchemes.untitled) { - const dirName = path.dirname(resource.path); - const fileName = this.inMemoryResourcePrefix + path.basename(resource.path); - return resource.with({ path: path.posix.join(dirName, fileName), query: '' }).toString(true); - } + switch (resource.scheme) { + case fileSchemes.file: + { + let result = resource.fsPath; + if (!result) { + return undefined; + } + result = path.normalize(result); - if (resource.scheme !== fileSchemes.file) { - return undefined; + // Both \ and / must be escaped in regular expressions + return result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/'); + } + default: + { + return this.inMemoryResourcePrefix + resource.toString(true); + } } - - let result = resource.fsPath; - if (!result) { - return undefined; - } - - if (resource.scheme === fileSchemes.file) { - result = path.normalize(result); - } - - // Both \ and / must be escaped in regular expressions - return result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/'); } public toPath(resource: vscode.Uri): string | undefined { @@ -641,20 +644,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public toResource(filepath: string): vscode.Uri { - if (filepath.match(/^[a-z]{2,}:/) || filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':')) - ) { - let resource = vscode.Uri.parse(filepath); - const dirName = path.dirname(resource.path); - const fileName = path.basename(resource.path); - if (fileName.startsWith(this.inMemoryResourcePrefix)) { - resource = resource.with({ - path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) - }); - } - + if (filepath.startsWith(this.inMemoryResourcePrefix)) { + const resource = vscode.Uri.parse(filepath.slice(1)); return this.bufferSyncSupport.toVsCodeResource(resource); } - return this.bufferSyncSupport.toResource(filepath); } @@ -732,9 +725,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType }); } - private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean, requireSemantic?: boolean }): undefined; + private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, requireSemantic?: boolean }): Promise>; + private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean, requireSemantic?: boolean }): Promise> | undefined { this.bufferSyncSupport.beforeCommand(command); const runningServerState = this.service(); return runningServerState.server.executeImpl(command, args, executeInfo); diff --git a/extensions/typescript-language-features/src/utils/dependentRegistration.ts b/extensions/typescript-language-features/src/utils/dependentRegistration.ts index a10a33ba80c..29597ab50cb 100644 --- a/extensions/typescript-language-features/src/utils/dependentRegistration.ts +++ b/extensions/typescript-language-features/src/utils/dependentRegistration.ts @@ -96,12 +96,12 @@ export function requireConfiguration( ); } -export function requireCapability( +export function requireSomeCapability( client: ITypeScriptServiceClient, - requiredCapability: ClientCapability, + ...capabilities: readonly ClientCapability[] ) { return new Condition( - () => client.capabilities.has(requiredCapability), + () => capabilities.some(requiredCapability => client.capabilities.has(requiredCapability)), client.onDidChangeCapabilities ); } diff --git a/extensions/typescript-language-features/src/utils/documentSelector.ts b/extensions/typescript-language-features/src/utils/documentSelector.ts new file mode 100644 index 00000000000..0574c3f587d --- /dev/null +++ b/extensions/typescript-language-features/src/utils/documentSelector.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export interface DocumentSelector { + /** + * Selector for files which only require a basic syntax server. + */ + readonly syntax: vscode.DocumentFilter[]; + + /** + * Selector for files which require semantic server support. + */ + readonly semantic: vscode.DocumentFilter[]; +} diff --git a/extensions/typescript-language-features/src/utils/fileSchemes.ts b/extensions/typescript-language-features/src/utils/fileSchemes.ts index 6efcfab4d3b..4e94d547bd6 100644 --- a/extensions/typescript-language-features/src/utils/fileSchemes.ts +++ b/extensions/typescript-language-features/src/utils/fileSchemes.ts @@ -8,12 +8,7 @@ export const untitled = 'untitled'; export const git = 'git'; export const walkThroughSnippet = 'walkThroughSnippet'; -export const supportedSchemes = [ +export const semanticSupportedSchemes = [ file, untitled, - walkThroughSnippet ]; - -export function isSupportedScheme(scheme: string): boolean { - return supportedSchemes.indexOf(scheme) >= 0; -} diff --git a/extensions/typescript-language-features/src/utils/fileSystem.ts b/extensions/typescript-language-features/src/utils/fileSystem.ts new file mode 100644 index 00000000000..a2531bc3703 --- /dev/null +++ b/extensions/typescript-language-features/src/utils/fileSystem.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import { getTempFile } from '../utils/temp'; + +export const onCaseInsenitiveFileSystem = (() => { + let value: boolean | undefined; + return (): boolean => { + if (typeof value === 'undefined') { + if (process.platform === 'win32') { + value = true; + } else if (process.platform !== 'darwin') { + value = false; + } else { + const temp = getTempFile('typescript-case-check'); + fs.writeFileSync(temp, ''); + value = fs.existsSync(temp.toUpperCase()); + } + } + return value; + }; +})(); diff --git a/extensions/typescript-language-features/src/utils/resourceMap.ts b/extensions/typescript-language-features/src/utils/resourceMap.ts index e942fae4583..ea4889ded35 100644 --- a/extensions/typescript-language-features/src/utils/resourceMap.ts +++ b/extensions/typescript-language-features/src/utils/resourceMap.ts @@ -3,10 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import * as vscode from 'vscode'; -import { memoize } from './memoize'; -import { getTempFile } from './temp'; /** * Maps of file resources @@ -18,7 +15,10 @@ export class ResourceMap { private readonly _map = new Map(); constructor( - private readonly _normalizePath: (resource: vscode.Uri) => string | undefined = (resource) => resource.fsPath + private readonly _normalizePath: (resource: vscode.Uri) => string | undefined = (resource) => resource.fsPath, + protected readonly config: { + readonly onCaseInsenitiveFileSystem: boolean, + }, ) { } public get size() { @@ -83,23 +83,10 @@ export class ResourceMap { if (isWindowsPath(path)) { return true; } - return path[0] === '/' && this.onIsCaseInsenitiveFileSystem; - } - - @memoize - private get onIsCaseInsenitiveFileSystem() { - if (process.platform === 'win32') { - return true; - } - if (process.platform !== 'darwin') { - return false; - } - const temp = getTempFile('typescript-case-check'); - fs.writeFileSync(temp, ''); - return fs.existsSync(temp.toUpperCase()); + return path[0] === '/' && this.config.onCaseInsenitiveFileSystem; } } -export function isWindowsPath(path: string): boolean { +function isWindowsPath(path: string): boolean { return /^[a-zA-Z]:[\/\\]/.test(path); } diff --git a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts b/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts index 8c32dedc3bf..b129064656e 100644 --- a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts @@ -79,7 +79,8 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { }]; } }, - executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined, _token: vscode.CancellationToken) => { + cancelAllCellsExecution: async () => { }, + executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined) => { if (!_cell) { _cell = _document.cells[0]; } @@ -92,6 +93,7 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { }]; return; }, + cancelCellExecution: async () => { } })); context.subscriptions.push(vscode.commands.registerCommand('vscode-notebook-tests.debugAction', async (cell: vscode.NotebookCell) => { diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index 33dd2960fda..3a80303a502 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -62,7 +62,7 @@ export function activate(context: vscode.ExtensionContext): any { context.subscriptions.push(vscode.notebook.registerNotebookKernel('notebookKernelTest', ['*.vsctestnb'], { label: 'Notebook Test Kernel', - executeAllCells: async (_document: vscode.NotebookDocument, _token: vscode.CancellationToken) => { + executeAllCells: async (_document: vscode.NotebookDocument) => { let cell = _document.cells[0]; cell.outputs = [{ @@ -73,7 +73,8 @@ export function activate(context: vscode.ExtensionContext): any { }]; return; }, - executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, _token: vscode.CancellationToken) => { + cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, + executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { if (!cell) { cell = document.cells[0]; } @@ -113,7 +114,8 @@ export function activate(context: vscode.ExtensionContext): any { } }); return; - } + }, + cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } })); const preloadUri = vscode.Uri.file(path.resolve(__dirname, '../src/customRenderer.js')); diff --git a/package.json b/package.json index 45ac42b7869..af86041877d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.48.0", - "distro": "b61767b72450bb972954ac50b830825554a107dc", + "distro": "a4dd690fc5b9555a6707734135e5e8ce0c35e5a5", "author": { "name": "Microsoft Corporation" }, @@ -108,7 +108,7 @@ "css-loader": "^3.2.0", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "8.3.3", + "electron": "9.1.0", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", diff --git a/remote/.yarnrc b/remote/.yarnrc index 1e16cde724c..c1a32ce532a 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,3 +1,3 @@ disturl "http://nodejs.org/dist" -target "12.4.0" +target "12.14.1" runtime "node" diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 817d95071f6..9c04f50698b 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -44,8 +44,8 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: Tests in the extension host -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% -if %errorlevel% neq 0 exit /b %errorlevel% +REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +REM if %errorlevel% neq 0 exit /b %errorlevel% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 2dc5ff578d0..f07f4dde622 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -49,7 +49,6 @@ fi ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR @@ -57,6 +56,7 @@ fi #"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR # Tests in commonJS cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index a8144075dd5..b0b266557a6 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -14,7 +14,7 @@ const nlsConfig = bootstrap.setupNLS(); // Bootstrap: Loader loader.config({ - baseUrl: bootstrap.uriFromPath(__dirname), + baseUrl: bootstrap.fileUriFromPath(__dirname), catchError: true, nodeRequire: require, nodeMain: __filename, diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index a417c1deb58..76c00585ad8 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -21,8 +21,9 @@ globalThis.MonacoBootstrapWindow = factory(); } }(this, function () { - const path = require.__$__nodeRequire('path'); - const bootstrap = globalThis.MonacoBootstrap; + const preloadGlobals = globals(); + const sandbox = preloadGlobals.context.sandbox; + const safeProcess = sandbox ? preloadGlobals.process : process; /** * @param {string[]} modulePaths @@ -43,29 +44,33 @@ const configuration = JSON.parse(args['config'] || '{}') || {}; // Error handler - process.on('uncaughtException', function (error) { + safeProcess.on('uncaughtException', function (error) { onUnexpectedError(error, enableDeveloperTools); }); // Developer tools - const enableDeveloperTools = (process.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath; + const enableDeveloperTools = (safeProcess.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath; let developerToolsUnbind; if (enableDeveloperTools || (options && options.forceEnableDeveloperKeybindings)) { developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding); } - // Correctly inherit the parent's environment - Object.assign(process.env, configuration.userEnv); + // Correctly inherit the parent's environment (TODO@sandbox non-sandboxed only) + if (!sandbox) { + Object.assign(safeProcess.env, configuration.userEnv); + } - // Enable ASAR support - bootstrap.enableASARSupport(path.join(configuration.appRoot, 'node_modules')); + // Enable ASAR support (TODO@sandbox non-sandboxed only) + if (!sandbox) { + globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot); + } if (options && typeof options.canModifyDOM === 'function') { options.canModifyDOM(configuration); } - // Get the nls configuration into the process.env as early as possible. - const nlsConfig = bootstrap.setupNLS(); + // Get the nls configuration into the process.env as early as possible (TODO@sandbox non-sandboxed only) + const nlsConfig = sandbox ? { availableLanguages: {} } : globalThis.MonacoBootstrap.setupNLS(); let locale = nlsConfig.availableLanguages['*'] || 'en'; if (locale === 'zh-tw') { @@ -76,16 +81,20 @@ window.document.documentElement.setAttribute('lang', locale); - // do not advertise AMD to avoid confusing UMD modules loaded with nodejs - window['define'] = undefined; + // do not advertise AMD to avoid confusing UMD modules loaded with nodejs (TODO@sandbox non-sandboxed only) + if (!sandbox) { + window['define'] = undefined; + } - // replace the patched electron fs with the original node fs for all AMD code - require.define('fs', ['original-fs'], function (originalFS) { return originalFS; }); + // replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only) + if (!sandbox) { + require.define('fs', ['original-fs'], function (originalFS) { return originalFS; }); + } window['MonacoEnvironment'] = {}; const loaderConfig = { - baseUrl: `${bootstrap.uriFromPath(configuration.appRoot)}/out`, + baseUrl: `${uriFromPath(configuration.appRoot)}/out`, 'vs/nls': nlsConfig, amdModulesPattern: /^vs\//, }; @@ -150,7 +159,7 @@ * @returns {() => void} */ function registerDeveloperKeybindings(disallowReloadKeybinding) { - const ipcRenderer = globals().ipcRenderer; + const ipcRenderer = preloadGlobals.ipcRenderer; const extractKey = function (e) { return [ @@ -163,9 +172,9 @@ }; // Devtools & reload support - const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I + const TOGGLE_DEV_TOOLS_KB = (safeProcess.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I const TOGGLE_DEV_TOOLS_KB_ALT = '123'; // F12 - const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R + const RELOAD_KB = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R let listener = function (e) { const key = extractKey(e); @@ -192,7 +201,7 @@ */ function onUnexpectedError(error, enableDeveloperTools) { if (enableDeveloperTools) { - const ipcRenderer = globals().ipcRenderer; + const ipcRenderer = preloadGlobals.ipcRenderer; ipcRenderer.send('vscode:openDevTools'); } @@ -211,6 +220,31 @@ return window.vscode; } + /** + * TODO@sandbox this should not use the file:// protocol at all + * and be consolidated with the fileUriFromPath() method in + * bootstrap.js. + * + * @param {string} path + * @returns {string} + */ + function uriFromPath(path) { + let pathName = path.replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = `/${pathName}`; + } + + /** @type {string} */ + let uri; + if (safeProcess.platform === 'win32' && pathName.startsWith('//')) { // specially handle Windows UNC paths + uri = encodeURI(`file:${pathName}`); + } else { + uri = encodeURI(`file://${pathName}`); + } + + return uri.replace(/#/g, '%23'); + } + return { load, globals diff --git a/src/bootstrap.js b/src/bootstrap.js index f1f579c7b7c..d1abd5502df 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -16,7 +16,11 @@ // Browser else { - globalThis.MonacoBootstrap = factory(); + try { + globalThis.MonacoBootstrap = factory(); + } catch (error) { + console.warn(error); // expected when e.g. running with sandbox: true (TODO@sandbox eventually consolidate this) + } } }(this, function () { const Module = require('module'); @@ -40,10 +44,10 @@ //#region Add support for using node_modules.asar /** - * @param {string=} nodeModulesPath + * @param {string} appRoot */ - function enableASARSupport(nodeModulesPath) { - let NODE_MODULES_PATH = nodeModulesPath; + function enableASARSupport(appRoot) { + let NODE_MODULES_PATH = appRoot ? path.join(appRoot, 'node_modules') : undefined; if (!NODE_MODULES_PATH) { NODE_MODULES_PATH = path.join(__dirname, '../node_modules'); } else { @@ -83,7 +87,7 @@ * @param {string} _path * @returns {string} */ - function uriFromPath(_path) { + function fileUriFromPath(_path) { let pathName = path.resolve(_path).replace(/\\/g, '/'); if (pathName.length > 0 && pathName.charAt(0) !== '/') { pathName = `/${pathName}`; @@ -132,7 +136,7 @@ } const bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`); - readFile(bundleFile).then(function (content) { + fs.promises.readFile(bundleFile, 'utf8').then(function (content) { const json = JSON.parse(content); bundles[bundle] = json; @@ -140,7 +144,7 @@ }).catch((error) => { try { if (nlsConfig._corruptedFile) { - writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + fs.promises.writeFile(nlsConfig._corruptedFile, 'corrupted', 'utf8').catch(function (error) { console.error(error); }); } } finally { cb(error, undefined); @@ -152,23 +156,6 @@ return nlsConfig; } - /** - * @param {string} file - * @returns {Promise} - */ - function readFile(file) { - return fs.promises.readFile(file, 'utf8'); - } - - /** - * @param {string} file - * @param {string} content - * @returns {Promise} - */ - function writeFile(file, content) { - return fs.promises.writeFile(file, content, 'utf8'); - } - //#endregion @@ -254,6 +241,6 @@ avoidMonkeyPatchFromAppInsights, configurePortable, setupNLS, - uriFromPath + fileUriFromPath }; })); diff --git a/src/main.js b/src/main.js index f179a672031..9a49d06217a 100644 --- a/src/main.js +++ b/src/main.js @@ -18,7 +18,11 @@ const bootstrap = require('./bootstrap'); const paths = require('./paths'); /** @type {any} */ const product = require('../product.json'); -const { app, protocol } = require('electron'); +const { app, protocol, crashReporter } = require('electron'); + +// Disable render process reuse, we still have +// non-context aware native modules in the renderer. +app.allowRendererProcessReuse = false; // Enable portable support const portable = bootstrap.configurePortable(product); @@ -31,13 +35,13 @@ const args = parseCLIArgs(); const userDataPath = getUserDataPath(args); app.setPath('userData', userDataPath); -// Set temp directory based on crash-reporter-directory CLI argument -// The crash reporter will store crashes in temp folder so we need -// to change that location accordingly. +// Configure static command line arguments +const argvConfig = configureCommandlineSwitchesSync(args); -// If a crash-reporter-directory is specified we setup the crash reporter -// right from the beginning as early as possible to monitor all processes. +// If a crash-reporter-directory is specified we store the crash reports +// in the specified directory and don't upload them to the crash server. let crashReporterDirectory = args['crash-reporter-directory']; +let submitURL = ''; if (crashReporterDirectory) { crashReporterDirectory = path.normalize(crashReporterDirectory); @@ -55,23 +59,41 @@ if (crashReporterDirectory) { } } - // Crashes are stored in the temp directory by default, so we + // Crashes are stored in the crashDumps directory by default, so we // need to change that directory to the provided one - console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`); - app.setPath('temp', crashReporterDirectory); - - // Start crash reporter - const { crashReporter } = require('electron'); - const productName = (product.crashReporter && product.crashReporter.productName) || product.nameShort; - const companyName = (product.crashReporter && product.crashReporter.companyName) || 'Microsoft'; - crashReporter.start({ - companyName: companyName, - productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, - submitURL: '', - uploadToServer: false - }); + console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); + app.setPath('crashDumps', crashReporterDirectory); +} else { + const appCenter = product.appCenter; + // Disable Appcenter crash reporting if + // * --crash-reporter-directory is specified + // * enable-crash-reporter runtime argument is set to 'false' + // * --disable-crash-reporter command line parameter is set + if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) { + const isWindows = (process.platform === 'win32'); + const isLinux = (process.platform === 'linux'); + const crashReporterId = argvConfig['crash-reporter-id']; + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (uuidPattern.test(crashReporterId)) { + submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin; + submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); + // Send the id for child node process that are explicitly starting crash reporter. + // For vscode this is ExtensionHost process currently. + process.argv.push('--crash-reporter-id', crashReporterId); + } + } } +// Start crash reporter for all processes +const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; +const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; +crashReporter.start({ + companyName: companyName, + productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, + submitURL, + uploadToServer: !crashReporterDirectory +}); + // Set logs path before app 'ready' event if running portable // to ensure that no 'logs' folder is created on disk at a // location outside of the portable directory @@ -110,9 +132,6 @@ registerListeners(); // Cached data const nodeCachedDataDir = getNodeCachedDir(); -// Configure static command line arguments -const argvConfig = configureCommandlineSwitchesSync(args); - // Remove env set by snap https://github.com/microsoft/vscode/issues/85344 if (process.env['SNAP']) { delete process.env['GDK_PIXBUF_MODULE_FILE']; @@ -254,9 +273,6 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('js-flags', jsFlags); } - // TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 - app.commandLine.appendSwitch('disable-features', 'LayoutNG'); - return argvConfig; } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 733d36ca30d..82acc8995b8 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 3f3177206e3..36696e4a455 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -476,6 +476,7 @@ export namespace Codicon { export const debugAltSmall = new Codicon('debug-alt-small', { character: '\\eba8' }); export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' }); export const cloud = new Codicon('cloud', { character: '\\ebaa' }); + export const merge = new Codicon('merge', { character: '\\ebab' }); } diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts index df362c06589..933e370f92c 100644 --- a/src/vs/base/common/json.ts +++ b/src/vs/base/common/json.ts @@ -72,6 +72,7 @@ export interface JSONScanner { } + export interface ParseError { error: ParseErrorCode; offset: number; diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index a45e0763ab1..cb3fe20dc79 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -332,6 +332,12 @@ export class Scrollable extends Disposable { this._setState(newState); + if (!this._smoothScrolling) { + // Looks like someone canceled the smooth scrolling + // from the scroll event handler + return; + } + if (update.isDone) { this._smoothScrolling.dispose(); this._smoothScrolling = null; diff --git a/src/vs/base/node/terminalEncoding.ts b/src/vs/base/node/terminalEncoding.ts index 63652abb1e5..fdfc268c48a 100644 --- a/src/vs/base/node/terminalEncoding.ts +++ b/src/vs/base/node/terminalEncoding.ts @@ -61,6 +61,10 @@ export async function resolveTerminalEncoding(verbose?: boolean): Promise { if (stdout) { + if (verbose) { + console.log(`Output from "chcp" command is: ${stdout}`); + } + const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings) as Array; for (const key of windowsTerminalEncodingKeys) { if (stdout.indexOf(key) >= 0) { diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 7b43355ed6d..a7fbfc81e6b 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -5,14 +5,14 @@ import { Menu, MenuItem, BrowserWindow, ipcMain, IpcMainEvent } from 'electron'; import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu'; +import { withNullAsUndefined } from 'vs/base/common/types'; export function registerContextMenuListener(): void { ipcMain.on(CONTEXT_MENU_CHANNEL, (event: IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => { const menu = createMenu(event, onClickChannel, items); - const window = BrowserWindow.fromWebContents(event.sender); menu.popup({ - window: window ? window : undefined, + window: withNullAsUndefined(BrowserWindow.fromWebContents(event.sender)), x: options ? options.x : undefined, y: options ? options.y : undefined, positioningItem: options ? options.positioningItem : undefined, diff --git a/src/vs/base/parts/sandbox/common/electronTypes.ts b/src/vs/base/parts/sandbox/common/electronTypes.ts index 8a5f4120862..c7729f338af 100644 --- a/src/vs/base/parts/sandbox/common/electronTypes.ts +++ b/src/vs/base/parts/sandbox/common/electronTypes.ts @@ -209,37 +209,6 @@ export interface SaveDialogReturnValue { bookmark?: string; } -export interface CrashReporterStartOptions { - companyName: string; - /** - * URL that crash reports will be sent to as POST. - */ - submitURL: string; - /** - * Defaults to `app.name`. - */ - productName?: string; - /** - * Whether crash reports should be sent to the server. Default is `true`. - */ - uploadToServer?: boolean; - /** - * Default is `false`. - */ - ignoreSystemCrashHandler?: boolean; - /** - * An object you can define that will be sent along with the report. Only string - * properties are sent correctly. Nested objects are not supported. When using - * Windows, the property names and values must be fewer than 64 characters. - */ - extra?: Record; - /** - * Directory to store the crash reports temporarily (only used when the crash - * reporter is started via `process.crashReporter.start`). - */ - crashesDirectory?: string; -} - export interface FileFilter { // Docs: http://electronjs.org/docs/api/structures/file-filter @@ -281,3 +250,62 @@ export interface MouseInputEvent extends InputEvent { x: number; y: number; } + +export interface CrashReporterStartOptions { + /** + * URL that crash reports will be sent to as POST. + */ + submitURL: string; + /** + * Defaults to `app.name`. + */ + productName?: string; + /** + * Deprecated alias for `{ globalExtra: { _companyName: ... } }`. + * + * @deprecated + */ + companyName?: string; + /** + * Whether crash reports should be sent to the server. If false, crash reports will + * be collected and stored in the crashes directory, but not uploaded. Default is + * `true`. + */ + uploadToServer?: boolean; + /** + * If true, crashes generated in the main process will not be forwarded to the + * system crash handler. Default is `false`. + */ + ignoreSystemCrashHandler?: boolean; + /** + * If true, limit the number of crashes uploaded to 1/hour. Default is `false`. + * + * @platform darwin,win32 + */ + rateLimit?: boolean; + /** + * If true, crash reports will be compressed and uploaded with `Content-Encoding: + * gzip`. Not all collection servers support compressed payloads. Default is + * `false`. + * + * @platform darwin,win32 + */ + compress?: boolean; + /** + * Extra string key/value annotations that will be sent along with crash reports + * that are generated in the main process. Only string values are supported. + * Crashes generated in child processes will not contain these extra parameters to + * crash reports generated from child processes, call `addExtraParameter` from the + * child process. + */ + extra?: Record; + /** + * Extra string key/value annotations that will be sent along with any crash + * reports generated in any process. These annotations cannot be changed once the + * crash reporter has been started. If a key is present in both the global extra + * parameters and the process-specific extra parameters, then the global one will + * take precedence. By default, `productName` and the app version are included, as + * well as the Electron version. + */ + globalExtra?: Record; +} diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index f0718ffc8e9..4dbe1b48a1d 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -7,16 +7,13 @@ (function () { 'use strict'; - const { ipcRenderer, webFrame, crashReporter } = require('electron'); + const { ipcRenderer, webFrame, crashReporter, contextBridge } = require('electron'); - // @ts-ignore - window.vscode = { + const globals = { /** - * A minimal set of methods exposed from ipcRenderer - * to support communication to electron-main - * - * @type {typeof import('../electron-sandbox/globals').ipcRenderer} + * A minimal set of methods exposed from Electron's `ipcRenderer` + * to support communication to main process. */ ipcRenderer: { @@ -25,9 +22,9 @@ * @param {any[]} args */ send(channel, ...args) { - validateIPC(channel); - - ipcRenderer.send(channel, ...args); + if (validateIPC(channel)) { + ipcRenderer.send(channel, ...args); + } }, /** @@ -35,9 +32,9 @@ * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener */ on(channel, listener) { - validateIPC(channel); - - ipcRenderer.on(channel, listener); + if (validateIPC(channel)) { + ipcRenderer.on(channel, listener); + } }, /** @@ -45,9 +42,9 @@ * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener */ once(channel, listener) { - validateIPC(channel); - - ipcRenderer.once(channel, listener); + if (validateIPC(channel)) { + ipcRenderer.once(channel, listener); + } }, /** @@ -55,16 +52,14 @@ * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener */ removeListener(channel, listener) { - validateIPC(channel); - - ipcRenderer.removeListener(channel, listener); + if (validateIPC(channel)) { + ipcRenderer.removeListener(channel, listener); + } } }, /** - * Support for methods of webFrame type. - * - * @type {typeof import('../electron-sandbox/globals').webFrame} + * Support for subset of methods of Electron's `webFrame` type. */ webFrame: { @@ -72,26 +67,71 @@ * @param {number} level */ setZoomLevel(level) { - webFrame.setZoomLevel(level); + if (typeof level === 'number') { + webFrame.setZoomLevel(level); + } } }, /** - * Support for methods of crashReporter type. - * - * @type {typeof import('../electron-sandbox/globals').crashReporter} + * Support for subset of methods of Electron's `crashReporter` type. */ crashReporter: { /** - * @param {Electron.CrashReporterStartOptions} options + * @param {string} key + * @param {string} value */ - start(options) { - crashReporter.start(options); + addExtraParameter(key, value) { + crashReporter.addExtraParameter(key, value); } + }, + + /** + * Support for a subset of access to node.js global `process`. + */ + process: { + platform: process.platform, + env: process.env, + on: + /** + * @param {string} type + * @param {() => void} callback + */ + function (type, callback) { + if (validateProcessEventType(type)) { + process.on(type, callback); + } + } + }, + + /** + * Some information about the context we are running in. + */ + context: { + sandbox: process.argv.includes('--enable-sandbox') } }; + // Use `contextBridge` APIs to expose globals to VSCode + // only if context isolation is enabled, otherwise just + // add to the DOM global. + let useContextBridge = process.argv.includes('--context-isolation'); + if (useContextBridge) { + try { + contextBridge.exposeInMainWorld('vscode', globals); + } catch (error) { + console.error(error); + + useContextBridge = false; + } + } + + if (!useContextBridge) { + // @ts-ignore + window.vscode = globals; + } + //#region Utilities /** @@ -101,6 +141,20 @@ if (!channel || !channel.startsWith('vscode:')) { throw new Error(`Unsupported event IPC channel '${channel}'`); } + + return true; + } + + /** + * @param {string} type + * @returns {type is 'uncaughtException'} + */ + function validateProcessEventType(type) { + if (type !== 'uncaughtException') { + throw new Error(`Unsupported process event '${type}'`); + } + + return true; } //#endregion diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 47d3700efd7..0acd55cb3fe 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes'; - export const ipcRenderer = (window as any).vscode.ipcRenderer as { /** @@ -54,30 +52,48 @@ export const webFrame = (window as any).vscode.webFrame as { export const crashReporter = (window as any).vscode.crashReporter as { /** - * You are required to call this method before using any other `crashReporter` APIs - * and in each process (main/renderer) from which you want to collect crash - * reports. You can pass different options to `crashReporter.start` when calling - * from different processes. + * Set an extra parameter to be sent with the crash report. The values specified + * here will be sent in addition to any values set via the `extra` option when + * `start` was called. * - * **Note** Child processes created via the `child_process` module will not have - * access to the Electron modules. Therefore, to collect crash reports from them, - * use `process.crashReporter.start` instead. Pass the same options as above along - * with an additional one called `crashesDirectory` that should point to a - * directory to store the crash reports temporarily. You can test this out by - * calling `process.crash()` to crash the child process. + * Parameters added in this fashion (or via the `extra` parameter to + * `crashReporter.start`) are specific to the calling process. Adding extra + * parameters in the main process will not cause those parameters to be sent along + * with crashes from renderer or other child processes. Similarly, adding extra + * parameters in a renderer process will not result in those parameters being sent + * with crashes that occur in other renderer processes or in the main process. * - * **Note:** If you need send additional/updated `extra` parameters after your - * first call `start` you can call `addExtraParameter` on macOS or call `start` - * again with the new/updated `extra` parameters on Linux and Windows. - * - * **Note:** On macOS and windows, Electron uses a new `crashpad` client for crash - * collection and reporting. If you want to enable crash reporting, initializing - * `crashpad` from the main process using `crashReporter.start` is required - * regardless of which process you want to collect crashes from. Once initialized - * this way, the crashpad handler collects crashes from all processes. You still - * have to call `crashReporter.start` from the renderer or child process, otherwise - * crashes from them will get reported without `companyName`, `productName` or any - * of the `extra` information. + * **Note:** Parameters have limits on the length of the keys and values. Key names + * must be no longer than 39 bytes, and values must be no longer than 127 bytes. + * Keys with names longer than the maximum will be silently ignored. Key values + * longer than the maximum length will be truncated. */ - start(options: CrashReporterStartOptions): void; + addExtraParameter(key: string, value: string): void; +}; + +export const process = (window as any).vscode.process as { + + /** + * The process.platform property returns a string identifying the operating system platform + * on which the Node.js process is running. + */ + platform: 'win32' | 'linux' | 'darwin'; + + /** + * The process.env property returns an object containing the user environment. See environ(7). + */ + env: { [key: string]: string | undefined }; + + /** + * A listener on the process. Only a small subset of listener types are allowed. + */ + on: (type: string, callback: Function) => void; +}; + +export const context = (window as any).vscode.context as { + + /** + * Wether the renderer runs with `sandbox` enabled or not. + */ + sandbox: boolean; }; diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 556c03a03ab..c629f7fffa1 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -12,6 +12,7 @@ import { request } from 'vs/base/parts/request/browser/request'; import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; import { isEqual } from 'vs/base/common/resources'; import { isStandalone } from 'vs/base/browser/browser'; +import { localize } from 'vs/nls'; interface ICredential { service: string; @@ -345,6 +346,11 @@ class WorkspaceProvider implements IWorkspaceProvider { // Finally create workbench create(document.body, { ...config, + homeIndicator: { + href: 'https://github.com/Microsoft/vscode', + icon: 'code', + title: localize('home', "Home") + }, workspaceProvider: new WorkspaceProvider(workspace, payload), urlCallbackProvider: new PollingURLCallbackProvider(), credentialsProvider: new LocalStorageCredentialsProvider() diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 221bea21443..6547f585aed 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -26,6 +26,6 @@ exports.collectModules = function () { createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []), createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), createModuleDescription('vs/platform/driver/node/driver', []), - createModuleDescription('vs/code/electron-browser/processExplorer/processExplorerMain', []) + createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain', []) ]; }; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 8b726400b97..e8ff8dda77a 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -82,6 +82,10 @@ import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMai import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; import { createServer, AddressInfo } from 'net'; import { IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; +import { IFileService } from 'vs/platform/files/common/files'; +import { stripComments } from 'vs/base/common/json'; +import { generateUuid } from 'vs/base/common/uuid'; +import { VSBuffer } from 'vs/base/common/buffer'; export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; @@ -134,11 +138,6 @@ export class CodeApplication extends Disposable { // // !!! DO NOT CHANGE without consulting the documentation !!! // - app.on('remote-get-guest-web-contents', event => { - this.logService.trace('App#on(remote-get-guest-web-contents): prevented'); - - event.preventDefault(); - }); app.on('remote-require', (event, sender, module) => { this.logService.trace('App#on(remote-require): prevented'); @@ -807,7 +806,7 @@ export class CodeApplication extends Disposable { return { fileUri: URI.file(path) }; } - private afterWindowOpen(accessor: ServicesAccessor): void { + private async afterWindowOpen(accessor: ServicesAccessor): Promise { // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; @@ -820,6 +819,34 @@ export class CodeApplication extends Disposable { if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { updateService.initialize(); } + + // If enable-crash-reporter argv is undefined then this is a fresh start, + // based on telemetry.enableCrashreporter settings, generate a UUID which + // will be used as crash reporter id and also update the json file. + try { + const fileService = accessor.get(IFileService); + const argvContent = await fileService.readFile(this.environmentService.argvResource); + const argvString = argvContent.value.toString(); + const argvJSON = JSON.parse(stripComments(argvString)); + if (argvJSON['enable-crash-reporter'] === undefined) { + const enableCrashReporter = this.configurationService.getValue('telemetry.enableCrashReporter') ?? true; + const additionalArgvContent = [ + '', + ' // Allows to disable crash reporting.', + ' // Should restart the app if the value is changed.', + ` "enable-crash-reporter": ${enableCrashReporter},`, + '', + ' // Unique id used for correlating crash reports sent from this instance.', + ' // Do not edit this value.', + ` "crash-reporter-id": "${generateUuid()}"`, + '}' + ]; + const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n')); + await fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString)); + } + } catch (error) { + this.logService.error(error); + } } private handleRemoteAuthorities(): void { diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index e03d4b8a527..f2eae8d0a6a 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -60,10 +60,12 @@ export class ProxyAuthHandler extends Disposable { title: 'VS Code', webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - enableWebSQL: false, sandbox: true, - devTools: false, + contextIsolation: true, + enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, + devTools: false, v8CacheOptions: 'bypassHeatCheck' } }; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 92027912edc..3d8b921d712 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -43,12 +43,13 @@ export class SharedProcess implements ISharedProcess { backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - images: false, nodeIntegration: true, - webgl: false, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, + images: false, + webgl: false, disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer } }); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index daf6df757cd..57400e616df 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -168,10 +168,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { webPreferences: { preload: URI.parse(this.doGetPreloadUrl()).fsPath, nodeIntegration: true, - webviewTag: true, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, + webviewTag: true, zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel) } }; diff --git a/src/vs/code/electron-browser/processExplorer/media/collapsed.svg b/src/vs/code/electron-sandbox/processExplorer/media/collapsed.svg similarity index 100% rename from src/vs/code/electron-browser/processExplorer/media/collapsed.svg rename to src/vs/code/electron-sandbox/processExplorer/media/collapsed.svg diff --git a/src/vs/code/electron-browser/processExplorer/media/expanded.svg b/src/vs/code/electron-sandbox/processExplorer/media/expanded.svg similarity index 100% rename from src/vs/code/electron-browser/processExplorer/media/expanded.svg rename to src/vs/code/electron-sandbox/processExplorer/media/expanded.svg diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css similarity index 100% rename from src/vs/code/electron-browser/processExplorer/media/processExplorer.css rename to src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css diff --git a/src/vs/code/electron-browser/processExplorer/processExplorer.html b/src/vs/code/electron-sandbox/processExplorer/processExplorer.html similarity index 100% rename from src/vs/code/electron-browser/processExplorer/processExplorer.html rename to src/vs/code/electron-sandbox/processExplorer/processExplorer.html diff --git a/src/vs/code/electron-browser/processExplorer/processExplorer.js b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js similarity index 84% rename from src/vs/code/electron-browser/processExplorer/processExplorer.js rename to src/vs/code/electron-sandbox/processExplorer/processExplorer.js index 97faa434a26..d6c6886e6fd 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorer.js +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js @@ -14,6 +14,6 @@ const bootstrapWindow = (() => { return window.MonacoBootstrapWindow; })(); -bootstrapWindow.load(['vs/code/electron-browser/processExplorer/processExplorerMain'], function (processExplorer, configuration) { - processExplorer.startup(configuration.data); +bootstrapWindow.load(['vs/code/electron-sandbox/processExplorer/processExplorerMain'], function (processExplorer, configuration) { + processExplorer.startup(configuration.windowId, configuration.data); }, { forceEnableDeveloperKeybindings: true }); diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts similarity index 85% rename from src/vs/code/electron-browser/processExplorer/processExplorerMain.ts rename to src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 59787a5a365..3c88a619a55 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -4,20 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/processExplorer'; -import { clipboard } from 'electron'; -import { totalmem } from 'os'; +import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import product from 'vs/platform/product/common/product'; import { localize } from 'vs/nls'; import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; -import * as platform from 'vs/base/common/platform'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; import { addDisposableListener, addClass } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; +import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/; @@ -40,7 +38,12 @@ class ProcessExplorer { private listeners = new DisposableStore(); - constructor(data: ProcessExplorerData) { + private electronService: IElectronService; + + constructor(windowId: number, private data: ProcessExplorerData) { + const mainProcessService = new MainProcessService(windowId); + this.electronService = new ElectronService(windowId, mainProcessService) as IElectronService; + this.applyStyles(data.styles); // Map window process pids to titles, annotate process names with this when rendering to distinguish between them @@ -59,24 +62,24 @@ class ProcessExplorer { ipcRenderer.send('vscode:listProcesses'); } - private getProcessList(rootProcess: ProcessItem, isLocal: boolean): FormattedProcessItem[] { + private getProcessList(rootProcess: ProcessItem, isLocal: boolean, totalMem: number): FormattedProcessItem[] { const processes: FormattedProcessItem[] = []; if (rootProcess) { - this.getProcessItem(processes, rootProcess, 0, isLocal); + this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem); } return processes; } - private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean): void { + private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number): void { const isRoot = (indent === 0); const MB = 1024 * 1024; let name = item.name; if (isRoot) { - name = isLocal ? `${product.applicationName} main` : 'remote agent'; + name = isLocal ? `${this.data.applicationName} main` : 'remote agent'; } if (name === 'window') { @@ -86,7 +89,7 @@ class ProcessExplorer { // Format name with indent const formattedName = isRoot ? name : `${' '.repeat(indent)} ${name}`; - const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100)); + const memory = this.data.platform === 'win32' ? item.mem : (totalMem * (item.mem / 100)); processes.push({ cpu: item.load, memory: (memory / MB), @@ -100,7 +103,7 @@ class ProcessExplorer { if (Array.isArray(item.children)) { item.children.forEach(child => { if (child) { - this.getProcessItem(processes, child, indent + 1, isLocal); + this.getProcessItem(processes, child, indent + 1, isLocal, totalMem); } }); } @@ -258,7 +261,7 @@ class ProcessExplorer { container.appendChild(body); } - private updateProcessInfo(processLists: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]): void { + private async updateProcessInfo(processLists: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]): Promise { const container = document.getElementById('process-list'); if (!container) { return; @@ -271,19 +274,20 @@ class ProcessExplorer { tableHead.innerHTML = ` ${localize('cpu', "CPU %")} ${localize('memory', "Memory (MB)")} - ${localize('pid', "pid")} + ${localize('pid', "PID")} ${localize('name', "Name")} `; container.append(tableHead); const hasMultipleMachines = Object.keys(processLists).length > 1; + const totalMem = await this.electronService.getTotalMem(); processLists.forEach((remote, i) => { const isLocal = i === 0; if (isRemoteDiagnosticError(remote.rootProcess)) { this.renderProcessFetchError(remote.name, remote.rootProcess.errorMessage); } else { - this.renderTableSection(remote.name, this.getProcessList(remote.rootProcess, isLocal), hasMultipleMachines, isLocal); + this.renderTableSection(remote.name, this.getProcessList(remote.rootProcess, isLocal, totalMem), hasMultipleMachines, isLocal); } }); } @@ -322,15 +326,15 @@ class ProcessExplorer { if (isLocal) { items.push({ label: localize('killProcess', "Kill Process"), - click() { - process.kill(pid, 'SIGTERM'); + click: () => { + this.electronService.killProcess(pid, 'SIGTERM'); } }); items.push({ label: localize('forceKillProcess', "Force Kill Process"), - click() { - process.kill(pid, 'SIGKILL'); + click: () => { + this.electronService.killProcess(pid, 'SIGKILL'); } }); @@ -341,20 +345,20 @@ class ProcessExplorer { items.push({ label: localize('copy', "Copy"), - click() { + click: () => { const row = document.getElementById(pid.toString()); if (row) { - clipboard.writeText(row.innerText); + this.electronService.writeClipboardText(row.innerText); } } }); items.push({ label: localize('copyAll', "Copy All"), - click() { + click: () => { const processList = document.getElementById('process-list'); if (processList) { - clipboard.writeText(processList.innerText); + this.electronService.writeClipboardText(processList.innerText); } } }); @@ -398,15 +402,15 @@ class ProcessExplorer { -export function startup(data: ProcessExplorerData): void { - const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac'; +export function startup(windowId: number, data: ProcessExplorerData): void { + const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac'; addClass(document.body, platformClass); // used by our fonts applyZoom(data.zoomLevel); - const processExplorer = new ProcessExplorer(data); + const processExplorer = new ProcessExplorer(windowId, data); document.onkeydown = (e: KeyboardEvent) => { - const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey; // Cmd/Ctrl + zooms in if (cmdOrCtrlKey && e.keyCode === 187) { @@ -421,7 +425,7 @@ export function startup(data: ProcessExplorerData): void { // Cmd/Ctrl + w closes process explorer window.addEventListener('keydown', e => { - const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey; if (cmdOrCtrlKey && e.keyCode === 87) { processExplorer.dispose(); ipcRenderer.send('vscode:closeProcessExplorer'); diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 121365ddbc6..3bde5dfd839 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -381,7 +381,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC } continue; } - if (typeof baseValue === 'object' && typeof subsetValue === 'object') { + if (baseValue && typeof baseValue === 'object' && subsetValue && typeof subsetValue === 'object') { if (!this._subsetEquals(baseValue, subsetValue)) { return false; } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index a366e265674..6176dcd8fa0 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2572,9 +2572,9 @@ class EditorPixelRatio extends ComputedEditorOption>; diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 3275621b978..c734735a733 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -153,9 +153,14 @@ export class LanguagesRegistry extends Disposable { } if (Array.isArray(lang.extensions)) { + if (lang.configuration) { + // insert first as this appears to be the 'primary' language definition + resolvedLanguage.extensions = lang.extensions.concat(resolvedLanguage.extensions); + } else { + resolvedLanguage.extensions = resolvedLanguage.extensions.concat(lang.extensions); + } for (let extension of lang.extensions) { mime.registerTextMime({ id: langId, mime: primaryMime, extension: extension }, this._warnOnOverwrite); - resolvedLanguage.extensions.push(extension); } } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index cb1f9a7a553..01b4416ffc5 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -101,6 +101,7 @@ export class ModesHoverController implements IEditorContribution { this._toUnhook.add(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); } else { this._toUnhook.add(this._editor.onMouseMove(hideWidgetsEventHandler)); + this._toUnhook.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); } this._toUnhook.add(this._editor.onMouseLeave(hideWidgetsEventHandler)); diff --git a/src/vs/editor/contrib/rename/media/onTypeRename.css b/src/vs/editor/contrib/rename/media/onTypeRename.css index 2c80c5957b1..16bb0178528 100644 --- a/src/vs/editor/contrib/rename/media/onTypeRename.css +++ b/src/vs/editor/contrib/rename/media/onTypeRename.css @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .on-type-rename-decoration { - background: rgba(255, 0, 0, 0.3); - border-left: 1px solid rgba(255, 0, 0, 0.3); + border-left: 1px solid transparent; /* So border can be transparent */ background-clip: padding-box; } diff --git a/src/vs/editor/contrib/rename/onTypeRename.ts b/src/vs/editor/contrib/rename/onTypeRename.ts index a7c6fd064fe..51cc31bbefe 100644 --- a/src/vs/editor/contrib/rename/onTypeRename.ts +++ b/src/vs/editor/contrib/rename/onTypeRename.ts @@ -26,6 +26,9 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { Color } from 'vs/base/common/color'; export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey('onTypeRenameInputVisible', false); @@ -360,6 +363,13 @@ export function getOnTypeRenameRanges(model: ITextModel, position: Position, tok }), result => !!result && arrays.isNonEmptyArray(result?.ranges)); } +export const editorOnTypeRenameBackground = registerColor('editor.onTypeRenameBackground', { dark: Color.fromHex('#f00').transparent(0.3), light: Color.fromHex('#f00').transparent(0.3), hc: Color.fromHex('#f00').transparent(0.3) }, nls.localize('editorOnTypeRenameBackground', 'Background color when the editor auto renames on type.')); +registerThemingParticipant((theme, collector) => { + const editorOnTypeRenameBackgroundColor = theme.getColor(editorOnTypeRenameBackground); + if (editorOnTypeRenameBackgroundColor) { + collector.addRule(`.monaco-editor .on-type-rename-decoration { background: ${editorOnTypeRenameBackgroundColor}; border-left-color: ${editorOnTypeRenameBackgroundColor}; }`); + } +}); registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None)); diff --git a/src/vs/editor/test/common/config/commonEditorConfig.test.ts b/src/vs/editor/test/common/config/commonEditorConfig.test.ts index c0196b37bd1..d0edc8f44c0 100644 --- a/src/vs/editor/test/common/config/commonEditorConfig.test.ts +++ b/src/vs/editor/test/common/config/commonEditorConfig.test.ts @@ -211,4 +211,15 @@ suite('Common Editor Config', () => { strings: false }); }); + + test('issue #102920: Can\'t snap or split view with JSON files', () => { + const config = new TestConfiguration({ quickSuggestions: null! }); + config.updateOptions({ quickSuggestions: { strings: true } }); + const actual = >>config.options.get(EditorOption.quickSuggestions); + assert.deepEqual(actual, { + other: true, + comments: false, + strings: true + }); + }); }); diff --git a/src/vs/editor/test/common/services/languagesRegistry.test.ts b/src/vs/editor/test/common/services/languagesRegistry.test.ts index 45c3ba17966..09ef74cd000 100644 --- a/src/vs/editor/test/common/services/languagesRegistry.test.ts +++ b/src/vs/editor/test/common/services/languagesRegistry.test.ts @@ -221,6 +221,32 @@ suite('LanguagesRegistry', () => { assert.deepEqual(registry.getExtensions('aName'), ['aExt', 'aExt2']); }); + test('extensions of primary language registration come first', () => { + let registry = new LanguagesRegistry(false); + + registry._registerLanguages([{ + id: 'a', + extensions: ['aExt3'] + }]); + + assert.deepEqual(registry.getExtensions('a')[0], 'aExt3'); + + registry._registerLanguages([{ + id: 'a', + configuration: URI.file('conf.json'), + extensions: ['aExt'] + }]); + + assert.deepEqual(registry.getExtensions('a')[0], 'aExt'); + + registry._registerLanguages([{ + id: 'a', + extensions: ['aExt2'] + }]); + + assert.deepEqual(registry.getExtensions('a')[0], 'aExt'); + }); + test('filenames', () => { let registry = new LanguagesRegistry(false); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d410ae9a711..0593f65eb4a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3557,9 +3557,9 @@ declare namespace monaco.editor { * Configuration options for quick suggestions */ export interface IQuickSuggestionsOptions { - other: boolean; - comments: boolean; - strings: boolean; + other?: boolean; + comments?: boolean; + strings?: boolean; } export type ValidQuickSuggestionsOptions = boolean | Readonly>; diff --git a/src/vs/platform/electron/common/electron.ts b/src/vs/platform/electron/common/electron.ts index d34b1c42a73..759bcd96ab3 100644 --- a/src/vs/platform/electron/common/electron.ts +++ b/src/vs/platform/electron/common/electron.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; +import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; @@ -64,6 +64,10 @@ export interface ICommonElectronService { updateTouchBar(items: ISerializableCommandAction[][]): Promise; moveItemToTrash(fullPath: string, deleteOnFail?: boolean): Promise; isAdmin(): Promise; + getTotalMem(): Promise; + + // Process + killProcess(pid: number, code: string): Promise; // clipboard readClipboardText(type?: 'selection' | 'clipboard'): Promise; @@ -94,7 +98,6 @@ export interface ICommonElectronService { // Development openDevTools(options?: OpenDevToolsOptions): Promise; toggleDevTools(): Promise; - startCrashReporter(options: CrashReporterStartOptions): Promise; sendInputEvent(event: MouseInputEvent): Promise; // Connectivity diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 12da10f915c..0c2f9a735ca 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron'; import { OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; @@ -20,9 +20,9 @@ import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; +import { totalmem } from 'os'; export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -37,8 +37,7 @@ export class ElectronMainService implements IElectronMainService { @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentService private readonly environmentService: INativeEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @ILogService private readonly logService: ILogService + @ITelemetryService private readonly telemetryService: ITelemetryService ) { } @@ -313,6 +312,19 @@ export class ElectronMainService implements IElectronMainService { return isAdmin; } + async getTotalMem(): Promise { + return totalmem(); + } + + //#endregion + + + //#region Process + + async killProcess(windowId: number | undefined, pid: number, code: string): Promise { + process.kill(pid, code); + } + //#endregion @@ -465,12 +477,6 @@ export class ElectronMainService implements IElectronMainService { } } - async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise { - this.logService.trace('ElectronMainService#crashReporter', JSON.stringify(options)); - - crashReporter.start(options); - } - async sendInputEvent(windowId: number | undefined, event: MouseInputEvent): Promise { const window = this.windowById(windowId); if (window && (event.type === 'mouseDown' || event.type === 'mouseUp')) { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index d3179e46b2f..92dd2bcf87d 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -64,18 +64,20 @@ export interface ParsedArgs { 'disable-updates'?: boolean; 'disable-crash-reporter'?: boolean; 'crash-reporter-directory'?: string; + 'crash-reporter-id'?: string; 'skip-add-to-recently-opened'?: boolean; 'max-memory'?: string; 'file-write'?: boolean; 'file-chmod'?: boolean; 'driver'?: string; 'driver-verbose'?: boolean; - remote?: string; + 'remote'?: string; 'disable-user-env-probe'?: boolean; 'force'?: boolean; 'do-not-sync'?: boolean; 'force-user-env'?: boolean; 'sync'?: 'on' | 'off'; + '__sandbox'?: boolean; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; @@ -181,6 +183,7 @@ export const OPTIONS: OptionDescriptions> = { 'disable-updates': { type: 'boolean' }, 'disable-crash-reporter': { type: 'boolean' }, 'crash-reporter-directory': { type: 'string' }, + 'crash-reporter-id': { type: 'string' }, 'disable-user-env-probe': { type: 'boolean' }, 'skip-add-to-recently-opened': { type: 'boolean' }, 'unity-launch': { type: 'boolean' }, @@ -195,6 +198,7 @@ export const OPTIONS: OptionDescriptions> = { 'trace-options': { type: 'string' }, 'force-user-env': { type: 'boolean' }, 'open-devtools': { type: 'boolean' }, + '__sandbox': { type: 'boolean' }, // chromium flags 'no-proxy-server': { type: 'boolean' }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 571418e3df3..45d5ec2cc02 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -43,6 +43,8 @@ export interface INativeEnvironmentService extends IEnvironmentService { driverVerbose: boolean; disableUpdates: boolean; + + sandbox: boolean; } export class EnvironmentService implements INativeEnvironmentService { @@ -254,7 +256,7 @@ export class EnvironmentService implements INativeEnvironmentService { get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } get disableUpdates(): boolean { return !!this._args['disable-updates']; } - get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; } + get crashReporterId(): string | undefined { return this._args['crash-reporter-id']; } get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; } get driverHandle(): string | undefined { return this._args['driver']; } @@ -262,6 +264,8 @@ export class EnvironmentService implements INativeEnvironmentService { get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; } + get sandbox(): boolean { return !!this._args['__sandbox']; } + constructor(private _args: ParsedArgs, private _execPath: string) { if (!process.env['VSCODE_LOGS']) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 0399e095367..4a9e5f8d4af 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -14,14 +14,15 @@ import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { rimraf } from 'vs/base/node/pfs'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export class ExtensionsLifecycle extends Disposable { private processesLimiter: Limiter = new Limiter(5); // Run max 5 processes in parallel constructor( - private environmentService: INativeEnvironmentService, - private logService: ILogService + @IEnvironmentService private environmentService: INativeEnvironmentService, + @ILogService private readonly logService: ILogService ) { super(); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index bdf3cdf40df..bfd0fbd7877 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -93,7 +93,8 @@ export class ExtensionManagementService extends Disposable implements IExtension @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner)); + const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle)); + this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension))); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader)); @@ -103,9 +104,6 @@ export class ExtensionManagementService extends Disposable implements IExtension this.installingExtensions.clear(); this.uninstallingExtensions.clear(); })); - - const extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService)); - this._register(this.extensionsScanner.onDidRemoveExtension(extension => extensionLifecycle.postUninstall(extension))); } zip(extension: ILocalExtension): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 8a02d584772..575b2aafc38 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -23,7 +23,6 @@ import { CancellationToken } from 'vscode'; import { extract, ExtractError } from 'vs/base/node/zip'; import { isWindows } from 'vs/base/common/platform'; import { flatten } from 'vs/base/common/arrays'; -import { Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; @@ -41,10 +40,8 @@ export class ExtensionsScanner extends Disposable { private readonly uninstalledPath: string; private readonly uninstalledFileLimiter: Queue; - private _onDidRemoveExtension = new Emitter(); - readonly onDidRemoveExtension = this._onDidRemoveExtension.event; - constructor( + private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @IProductService private readonly productService: IProductService, @@ -280,7 +277,7 @@ export class ExtensionsScanner extends Disposable { await Promise.all(byExtension.map(async e => { const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]; if (!installed.has(latest.identifier.id.toLowerCase())) { - this._onDidRemoveExtension.fire(latest); + await this.beforeRemovingExtension(latest); } })); const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index d95587fce0a..d82ba0948de 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -85,6 +85,8 @@ export interface ProcessExplorerStyles extends WindowStyles { export interface ProcessExplorerData extends WindowData { pid: number; styles: ProcessExplorerStyles; + platform: string; + applicationName: string; } export interface ICommonIssueService { diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 532a3bd4e59..3621783e893 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -199,6 +199,7 @@ export class IssueMainService implements ICommonIssueService { nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, zoomFactor: zoomLevelToZoomFactor(data.zoomLevel) } @@ -250,11 +251,23 @@ export class IssueMainService implements ICommonIssueService { title: localize('processExplorer', "Process Explorer"), webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel) + zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), + ...this.environmentService.sandbox ? + + // Sandbox + { + sandbox: true, + contextIsolation: true + } : + + // No Sandbox + { + nodeIntegration: true + } } }); @@ -270,7 +283,7 @@ export class IssueMainService implements ICommonIssueService { }; this._processExplorerWindow.loadURL( - toLauchUrl('vs/code/electron-browser/processExplorer/processExplorer.html', windowConfiguration)); + toLauchUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration)); this._processExplorerWindow.on('close', () => this._processExplorerWindow = null); diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 40a2cf28940..f1b54f87e9d 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -70,14 +70,18 @@ export class LaunchMainService implements ILaunchMainService { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise { + async start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise { this.logService.trace('Received data from other instance: ', args, userEnv); - const urlsToOpen = parseOpenUrl(args); + // Since we now start to open a window, make sure the app has focus. + // Focussing a window will not ensure that the application itself + // has focus, so we use the `steal: true` hint to force focus. + app.focus({ steal: true }); // Check early for open-url which is handled in URL service + const urlsToOpen = parseOpenUrl(args); if (urlsToOpen.length) { - let whenWindowReady: Promise = Promise.resolve(null); + let whenWindowReady: Promise = Promise.resolve(); // Create a window if there is none if (this.windowsMainService.getWindowCount() === 0) { @@ -91,12 +95,12 @@ export class LaunchMainService implements ILaunchMainService { this.urlService.open(url); } }); - - return Promise.resolve(undefined); } // Otherwise handle in windows service - return this.startOpenWindow(args, userEnv); + else { + return this.startOpenWindow(args, userEnv); + } } private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): Promise { @@ -156,8 +160,6 @@ export class LaunchMainService implements ILaunchMainService { else { const lastActive = this.windowsMainService.getLastActiveWindow(); if (lastActive) { - // Force focus the app before requesting window focus - app.focus({ steal: true }); lastActive.focus(); usedWindows = [lastActive]; diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 9db4e9afcda..8645255d9e7 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -10,7 +10,7 @@ import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/b import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { generateUuid } from 'vs/base/common/uuid'; -import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; type Key = string; type Value = string; @@ -49,16 +49,6 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`); } - // This is unique to the application instance and thereby - // should be written from the main process once. - // - // THIS SHOULD NEVER BE SENT TO TELEMETRY. - // - const crashReporterId = this.storageMainService.get(crashReporterIdStorageKey, undefined); - if (crashReporterId === undefined) { - this.storageMainService.store(crashReporterIdStorageKey, generateUuid()); - } - // Apply global telemetry values as part of the initialization // These are global across all windows and thereby should be // written from the main process once. diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 5b165a34e29..e0a9a30e55c 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -55,4 +55,3 @@ export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; export const machineIdKey = 'telemetry.machineId'; -export const crashReporterIdStorageKey = 'crashReporter.guid'; diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 4aa6495562c..466d0f81a84 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -384,7 +384,7 @@ export abstract class AbstractSynchroniser extends Disposable { } } - async accept(resource: URI, content: string): Promise { + async accept(resource: URI, content: string | null): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content); return { @@ -397,7 +397,7 @@ export abstract class AbstractSynchroniser extends Disposable { async merge(resource: URI): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { - const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent || ''); + const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent); return { ...updatedResourcePreview, mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted @@ -409,7 +409,7 @@ export abstract class AbstractSynchroniser extends Disposable { async discard(resource: URI): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || '')); - const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent || ''); + const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent); return { ...updatedResourcePreview, mergeState: MergeState.Preview @@ -448,7 +448,7 @@ export abstract class AbstractSynchroniser extends Disposable { } } - protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string): Promise { + protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise { return { ...resourcePreview, acceptedContent @@ -550,13 +550,13 @@ export abstract class AbstractSynchroniser extends Disposable { if (syncPreview) { for (const resourcePreview of syncPreview.resourcePreviews) { if (isEqual(resourcePreview.acceptedResource, uri)) { - return resourcePreview.acceptedContent || ''; + return resourcePreview.acceptedContent; } if (isEqual(resourcePreview.remoteResource, uri)) { - return resourcePreview.remoteContent || ''; + return resourcePreview.remoteContent; } if (isEqual(resourcePreview.localResource, uri)) { - return resourcePreview.localContent || ''; + return resourcePreview.localContent; } } } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 5101b59bf25..7cc8738fdf9 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -183,7 +183,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } } - protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string): Promise { + protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string | null): Promise { if (isEqual(resource, this.localResource)) { const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; return this.getPushPreview(remoteExtensions); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index f04ae509afb..de10325223a 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -178,7 +178,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string): Promise { + protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise { if (isEqual(this.localResource, resource)) { return this.getPushPreview(resourcePreview.remoteContent); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index a8579c8f5de..87d352779bf 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -68,7 +68,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); let previewContent: string | null = null; - if (remoteSettingsSyncContent !== null) { + if (remoteSettingsSyncContent) { // Update ignored settings from local file content previewContent = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils); } @@ -96,10 +96,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const ignoredSettings = await this.getIgnoredSettings(); const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - let previewContent: string | null = null; - if (fileContent !== null) { + let previewContent: string | null = fileContent?.value.toString() || null; + if (previewContent) { // Remove ignored settings - previewContent = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils); + previewContent = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formatUtils); } return [{ @@ -199,8 +199,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement }]; } - protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string): Promise { - if (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource)) { + protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { + if (acceptedContent && (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource))) { const formatUtils = await this.getFormattingOptions(); // Add ignored settings from local file content const ignoredSettings = await this.getIgnoredSettings(); @@ -297,7 +297,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement protected async resolvePreviewContent(resource: URI): Promise { let content = await super.resolvePreviewContent(resource); - if (content !== null) { + if (content) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the preview content const ignoredSettings = await this.getIgnoredSettings(); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 3842f4513b6..b25c7d727bd 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -96,12 +96,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); } - protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string): Promise { + protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { return { ...resourcePreview, - acceptedContent: acceptedContent || null, - localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent || null), - remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent || null), + acceptedContent, + localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent), + remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent), }; } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 511df050312..c6dc7a03736 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -15,6 +15,7 @@ import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { localize } from 'vs/nls'; +import { toLocalISOString } from 'vs/base/common/date'; type AutoSyncClassification = { sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -96,18 +97,24 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i this.syncTriggerDelayer = this._register(new Delayer(0)); if (userDataSyncStoreService.userDataSyncStore) { + if (this.isEnabled()) { + this.logService.info('Auto Sync is enabled.'); + } else { + this.logService.info('Auto Sync is disabled.'); + } this.updateAutoSync(); if (this.hasToDisableMachineEventually()) { this.disableMachineEventually(); } this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync())); + this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync())); this._register(Event.debounce(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false))); this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false))); } } private updateAutoSync(): void { - const { enabled, reason } = this.isAutoSyncEnabled(); + const { enabled, message } = this.isAutoSyncEnabled(); if (enabled) { if (this.autoSync.value === undefined) { this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncStoreService, this.userDataSyncService, this.userDataSyncMachinesService, this.logService, this.storageService); @@ -120,21 +127,31 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i } else { this.syncTriggerDelayer.cancel(); if (this.autoSync.value !== undefined) { - this.logService.info('Auto Sync: Disabled because', reason); + if (message) { + this.logService.info(message); + } this.autoSync.clear(); } + + /* log message when auto sync is not disabled by user */ + else if (message && this.isEnabled()) { + this.logService.info(message); + } } } // For tests purpose only protected startAutoSync(): boolean { return true; } - private isAutoSyncEnabled(): { enabled: boolean, reason?: string } { + private isAutoSyncEnabled(): { enabled: boolean, message?: string } { if (!this.isEnabled()) { - return { enabled: false, reason: 'sync is disabled' }; + return { enabled: false, message: 'Auto Sync: Disabled.' }; } if (!this.userDataSyncAccountService.account) { - return { enabled: false, reason: 'token is not avaialable' }; + return { enabled: false, message: 'Auto Sync: Suspended until auth token is available.' }; + } + if (this.userDataSyncStoreService.donotMakeRequestsUntil) { + return { enabled: false, message: `Auto Sync: Suspended until ${toLocalISOString(this.userDataSyncStoreService.donotMakeRequestsUntil)} because server is not accepting requests until then.` }; } return { enabled: true }; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 0cd0adf6049..782a2db69c5 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -164,6 +164,9 @@ export interface IUserDataSyncStoreService { readonly _serviceBrand: undefined; readonly userDataSyncStore: IUserDataSyncStore | undefined; + readonly onDidChangeDonotMakeRequestsUntil: Event; + readonly donotMakeRequestsUntil: Date | undefined; + readonly onTokenFailed: Event; readonly onTokenSucceed: Event; setAuthToken(token: string, type: string): void; @@ -207,6 +210,7 @@ export enum UserDataSyncErrorCode { UpgradeRequired = 'UpgradeRequired', /* 426 */ PreconditionRequired = 'PreconditionRequired', /* 428 */ TooManyRequests = 'RemoteTooManyRequests', /* 429 */ + TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */ // Local Errors ConnectionRefused = 'ConnectionRefused', @@ -356,7 +360,7 @@ export interface IUserDataSynchroniser { stop(): Promise; preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise; - accept(resource: URI, content: string): Promise; + accept(resource: URI, content: string | null): Promise; merge(resource: URI): Promise; discard(resource: URI): Promise; apply(force: boolean, headers: IHeaders): Promise; @@ -396,7 +400,7 @@ export interface IManualSyncTask extends IDisposable { readonly manifest: IUserDataManifest | null; readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>; preview(): Promise<[SyncResource, ISyncResourcePreview][]>; - accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]>; + accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]>; merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>; discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>; apply(): Promise<[SyncResource, ISyncResourcePreview][]>; @@ -433,7 +437,7 @@ export interface IUserDataSyncService { hasLocalData(): Promise; hasPreviouslySynced(): Promise; resolveContent(resource: URI): Promise; - accept(resource: SyncResource, conflictResource: URI, content: string, apply: boolean): Promise; + accept(resource: SyncResource, conflictResource: URI, content: string | null, apply: boolean): Promise; getLocalSyncResourceHandles(resource: SyncResource): Promise; getRemoteSyncResourceHandles(resource: SyncResource): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 97789537313..015faac4543 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -65,7 +65,7 @@ export class UserDataSyncChannel implements IServerChannel { private async createManualSyncTask(): Promise<{ id: string, manifest: IUserDataManifest | null }> { const manualSyncTask = await this.service.createManualSyncTask(); - const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask); + const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask, this.logService); this.server.registerChannel(`manualSyncTask-${manualSyncTask.id}`, manualSyncTaskChannel); return { id: manualSyncTask.id, manifest: manualSyncTask.manifest }; } @@ -73,7 +73,10 @@ export class UserDataSyncChannel implements IServerChannel { class ManualSyncTaskChannel implements IServerChannel { - constructor(private readonly manualSyncTask: IManualSyncTask) { } + constructor( + private readonly manualSyncTask: IManualSyncTask, + private readonly logService: ILogService + ) { } listen(_: unknown, event: string): Event { switch (event) { @@ -83,6 +86,16 @@ class ManualSyncTaskChannel implements IServerChannel { } async call(context: any, command: string, args?: any): Promise { + try { + const result = await this._call(context, command, args); + return result; + } catch (e) { + this.logService.error(e); + throw e; + } + } + + private async _call(context: any, command: string, args?: any): Promise { switch (command) { case 'preview': return this.manualSyncTask.preview(); case 'accept': return this.manualSyncTask.accept(URI.revive(args[0]), args[1]); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 7e3485683cc..7d5dd64ce26 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -5,7 +5,7 @@ import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode, - UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID + UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -102,11 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.checkEnablement(); try { for (const synchroniser of this.synchronisers) { - try { - await synchroniser.pull(); - } catch (e) { - this.handleSynchronizerError(e, synchroniser.resource); - } + await synchroniser.pull(); } this.updateLastSyncTime(); } catch (error) { @@ -121,11 +117,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.checkEnablement(); try { for (const synchroniser of this.synchronisers) { - try { - await synchroniser.push(); - } catch (e) { - this.handleSynchronizerError(e, synchroniser.resource); - } + await synchroniser.push(); } this.updateLastSyncTime(); } catch (error) { @@ -264,7 +256,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async accept(syncResource: SyncResource, resource: URI, content: string, apply: boolean): Promise { + async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise { await this.checkEnablement(); const synchroniser = this.getSynchroniser(syncResource); await synchroniser.accept(resource, content); @@ -402,6 +394,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ throw new UserDataSyncError(e.message, e.code, source); case UserDataSyncErrorCode.TooManyRequests: + case UserDataSyncErrorCode.TooManyRequestsAndRetryAfter: case UserDataSyncErrorCode.LocalTooManyRequests: case UserDataSyncErrorCode.Gone: case UserDataSyncErrorCode.UpgradeRequired: @@ -463,7 +456,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { return this.previews; } - async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> { + async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { return this.performAction(resource, sychronizer => sychronizer.accept(resource, content)); } @@ -527,11 +520,22 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { for (const [syncResource, preview] of this.previews) { this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]); this._onSynchronizeResources.fire(this.synchronizingResources); + const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; + + /* merge those which are not yet merged */ + for (const resourcePreview of preview.resourcePreviews) { + if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) { + await synchroniser.merge(resourcePreview.previewResource); + } + } + + /* apply */ const newPreview = await synchroniser.apply(false, this.syncHeaders); if (newPreview) { previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview)); } + this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); this._onSynchronizeResources.fire(this.synchronizingResources); } @@ -551,7 +555,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { this._onSynchronizeResources.fire(this.synchronizingResources); const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; for (const resourcePreview of preview.resourcePreviews) { - const content = await synchroniser.resolveContent(resourcePreview.remoteResource) || ''; + const content = await synchroniser.resolveContent(resourcePreview.remoteResource); await synchroniser.accept(resourcePreview.remoteResource, content); } await synchroniser.apply(true, this.syncHeaders); @@ -573,7 +577,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { this._onSynchronizeResources.fire(this.synchronizingResources); const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; for (const resourcePreview of preview.resourcePreviews) { - const content = await synchroniser.resolveContent(resourcePreview.localResource) || ''; + const content = await synchroniser.resolveContent(resourcePreview.localResource); await synchroniser.accept(resourcePreview.localResource, content); } await synchroniser.apply(true, this.syncHeaders); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index fd972f83e84..2c0e43d4a5d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -19,7 +19,9 @@ import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { isWeb } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; +import { createCancelablePromise, timeout, CancelablePromise } from 'vs/base/common/async'; +const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until'; const USER_SESSION_ID_KEY = 'sync.user-session-id'; const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id'; const REQUEST_SESSION_LIMIT = 100; @@ -40,6 +42,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn private _onTokenSucceed: Emitter = this._register(new Emitter()); readonly onTokenSucceed: Event = this._onTokenSucceed.event; + private _donotMakeRequestsUntil: Date | undefined = undefined; + get donotMakeRequestsUntil() { return this._donotMakeRequestsUntil; } + private _onDidChangeDonotMakeRequestsUntil = this._register(new Emitter()); + readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event; + constructor( @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, @@ -66,12 +73,41 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn /* A requests session that limits requests per sessions */ this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, this.logService); + this.initDonotMakeRequestsUntil(); } setAuthToken(token: string, type: string): void { this.authToken = { token, type }; } + private initDonotMakeRequestsUntil(): void { + const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL); + if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) { + this.setDonotMakeRequestsUntil(new Date(donotMakeRequestsUntil)); + } + } + + private resetDonotMakeRequestsUntilPromise: CancelablePromise | undefined = undefined; + private setDonotMakeRequestsUntil(donotMakeRequestsUntil: Date | undefined): void { + if (this._donotMakeRequestsUntil?.getTime() !== donotMakeRequestsUntil?.getTime()) { + this._donotMakeRequestsUntil = donotMakeRequestsUntil; + + if (this.resetDonotMakeRequestsUntilPromise) { + this.resetDonotMakeRequestsUntilPromise.cancel(); + this.resetDonotMakeRequestsUntilPromise = undefined; + } + + if (this._donotMakeRequestsUntil) { + this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL); + this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined))); + } else { + this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL); + } + + this._onDidChangeDonotMakeRequestsUntil.fire(); + } + } + async getAllRefs(resource: ServerResource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); @@ -244,6 +280,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, undefined); } + if (this._donotMakeRequestsUntil && Date.now() < this._donotMakeRequestsUntil.getTime()) { + throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined); + } + this.setDonotMakeRequestsUntil(undefined); + const commonHeaders = await this.commonHeadersPromise; options.headers = assign(options.headers || {}, commonHeaders, { 'X-Account-Type': this.authToken.type, @@ -299,7 +340,13 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } if (context.res.statusCode === 429) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId); + const retryAfter = context.res.headers['retry-after']; + if (retryAfter) { + this.setDonotMakeRequestsUntil(new Date(Date.now() + (parseInt(retryAfter) * 1000))); + throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId); + } else { + throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId); + } } return context; diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 9fd790befce..49d439bfd60 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -83,4 +83,20 @@ suite('KeybindingsSync', () => { assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]'); }); + test('test apply remote when keybindings file does not exist', async () => { + const fileService = client.instantiationService.get(IFileService); + const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource; + if (await fileService.exists(keybindingsResource)) { + await fileService.del(keybindingsResource); + } + + const preview = (await testObject.preview(await client.manifest()))!; + + server.reset(); + const content = await testObject.resolveContent(preview.resourcePreviews[0].remoteResource); + await testObject.accept(preview.resourcePreviews[0].remoteResource, content); + await testObject.apply(false); + assert.deepEqual(server.requests, []); + }); + }); diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 394cfa26e3e..0115fe44f8d 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -596,7 +596,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); - await testObject.accept(testObject.conflicts[0].previewResource, ''); + await testObject.accept(testObject.conflicts[0].previewResource, null); await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 4626457a03c..310c69dd6cf 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -383,4 +383,22 @@ suite('UserDataAutoSyncService', () => { assert.deepEqual((e).code, UserDataSyncErrorCode.TooManyRequests); }); + test('test auto sync is suspended when server donot accepts requests', async () => { + const target = new UserDataSyncTestServer(5, 1); + + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService); + + while (target.requests.length < 5) { + await testObject.sync(); + } + + target.reset(); + await testObject.sync(); + + assert.deepEqual(target.requests, []); + }); + }); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 0be19fe237d..d7587581aaf 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -154,13 +154,13 @@ export class UserDataSyncTestServer implements IRequestService { get responses(): { status: number }[] { return this._responses; } reset(): void { this._requests = []; this._responses = []; this._requestsWithAllHeaders = []; } - constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER) { } + constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER, private readonly retryAfter?: number) { } async resolveProxy(url: string): Promise { return url; } async request(options: IRequestOptions, token: CancellationToken): Promise { if (this._requests.length === this.rateLimit) { - return this.toResponse(429); + return this.toResponse(429, this.retryAfter ? { 'retry-after': `${this.retryAfter}` } : undefined); } const headers: IHeaders = {}; if (options.headers) { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index b65dbb71836..f732c47f0b2 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -9,12 +9,13 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData import { DisposableStore } from 'vs/base/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/productService'; import { isWeb } from 'vs/base/common/platform'; -import { RequestsSession } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { RequestsSession, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IRequestService } from 'vs/platform/request/common/request'; import { newWriteableBufferStream } from 'vs/base/common/buffer'; import { timeout } from 'vs/base/common/async'; import { NullLogService } from 'vs/platform/log/common/log'; +import { Event } from 'vs/base/common/event'; suite('UserDataSyncStoreService', () => { @@ -322,6 +323,71 @@ suite('UserDataSyncStoreService', () => { assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined); }); + test('test rate limit on server with retry after', async () => { + const target = new UserDataSyncTestServer(1, 1); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncStoreService); + + await testObject.manifest(); + + const promise = Event.toPromise(testObject.onDidChangeDonotMakeRequestsUntil); + try { + await testObject.manifest(); + assert.fail('should fail'); + } catch (e) { + assert.ok(e instanceof UserDataSyncStoreError); + assert.deepEqual((e).code, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter); + await promise; + assert.ok(!!testObject.donotMakeRequestsUntil); + } + }); + + test('test donotMakeRequestsUntil is reset after retry time is finished', async () => { + const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 0.25))); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncStoreService); + + await testObject.manifest(); + try { + await testObject.manifest(); + } catch (e) { } + + const promise = Event.toPromise(testObject.onDidChangeDonotMakeRequestsUntil); + await timeout(300); + await promise; + assert.ok(!testObject.donotMakeRequestsUntil); + }); + + test('test donotMakeRequestsUntil is retrieved', async () => { + const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 1))); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncStoreService); + + await testObject.manifest(); + try { + await testObject.manifest(); + } catch (e) { } + + const target = client.instantiationService.createInstance(UserDataSyncStoreService); + assert.equal(target.donotMakeRequestsUntil?.getTime(), testObject.donotMakeRequestsUntil?.getTime()); + }); + + test('test donotMakeRequestsUntil is checked and reset after retreived', async () => { + const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 0.25))); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncStoreService); + + await testObject.manifest(); + try { + await testObject.manifest(); + } catch (e) { } + + await timeout(300); + const target = client.instantiationService.createInstance(UserDataSyncStoreService); + assert.ok(!target.donotMakeRequestsUntil); + }); + }); suite('UserDataSyncRequestsSession', () => { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index fe29cc7149b..efd2b3b06e4 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9868,11 +9868,12 @@ declare module 'vscode' { export interface ConfigurationChangeEvent { /** - * Returns `true` if the given section is affected in the provided scope. + * Checks if the given section has changed. + * If scope is provided, checks if the section has changed for resources under the given scope. * * @param section Configuration name, supports _dotted_ names. * @param scope A scope in which to check. - * @return `true` if the given section is affected in the provided scope. + * @return `true` if the given section has changed. */ affectsConfiguration(section: string, scope?: ConfigurationScope): boolean; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4b8dcf46709..086d6ab453d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1423,6 +1423,11 @@ declare module 'vscode' { Error = 4 } + export enum NotebookRunState { + Running = 1, + Idle = 2 + } + export interface NotebookCellMetadata { /** * Controls if the content of a cell is editable or not. @@ -1525,6 +1530,11 @@ declare module 'vscode' { * Additional attributes of the document metadata. */ custom?: { [key: string]: any }; + + /** + * The document's current run state + */ + runState?: NotebookRunState; } export interface NotebookDocument { @@ -1593,6 +1603,11 @@ declare module 'vscode' { */ readonly onDidDispose: Event; + /** + * Active kernel used in the editor + */ + readonly kernel?: NotebookKernel; + /** * Fired when the output hosting webview posts a message. */ @@ -1822,12 +1837,15 @@ declare module 'vscode' { } export interface NotebookKernel { + readonly id?: string; label: string; description?: string; isPreferred?: boolean; preloads?: Uri[]; - executeCell(document: NotebookDocument, cell: NotebookCell, token: CancellationToken): Promise; - executeAllCells(document: NotebookDocument, token: CancellationToken): Promise; + executeCell(document: NotebookDocument, cell: NotebookCell): void; + cancelCellExecution(document: NotebookDocument, cell: NotebookCell): void; + executeAllCells(document: NotebookDocument): void; + cancelAllCellsExecution(document: NotebookDocument): void; } export interface NotebookDocumentFilter { @@ -1890,8 +1908,7 @@ declare module 'vscode' { */ export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; - export let activeNotebookKernel: NotebookKernel | undefined; - export const onDidChangeActiveNotebookKernel: Event; + export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined }>; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 878675c6f39..7e9c0ae5094 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -17,6 +17,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { fromNow } from 'vs/base/common/date'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser']; @@ -213,7 +214,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu @INotificationService private readonly notificationService: INotificationService, @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IExtensionService private readonly extensionService: IExtensionService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); @@ -292,7 +294,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } const session = await this.authenticationService.login(providerId, scopes); - await this.$setTrustedExtension(providerId, session.account.label, extensionId, extensionName); + await this.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id); return session; } else { await this.$requestNewSession(providerId, scopes, extensionId, extensionName); @@ -378,6 +380,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise { + await this.extensionService.activateByEvent(`onAuthenticationRequest:${providerId}`); + const allowList = readAllowedExtensions(this.storageService, providerId, accountName); const extensionData = allowList.find(extension => extension.id === extensionId); if (extensionData) { @@ -423,11 +427,13 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu return choice === 0; } - async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { + async $setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise { const allowList = readAllowedExtensions(this.storageService, providerId, accountName); if (!allowList.find(allowed => allowed.id === extensionId)) { allowList.push({ id: extensionId, name: extensionName }); this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); } + + this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL); } } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 2b63e4e286e..5c8d9209449 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData } from '../common/extHost.protocol'; -import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta } from '../common/extHost.protocol'; +import { Disposable, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -19,8 +18,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IRelativePattern } from 'vs/base/common/glob'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { Emitter } from 'vs/base/common/event'; @@ -55,42 +53,8 @@ export class MainThreadNotebookDocument extends Disposable { })); } - async applyEdit(modelVersionId: number, edits: ICellEditOperation[], emitToExtHost: boolean, synchronous: boolean): Promise { - await this.notebookService.transformEditsOutputs(this.textModel, edits); - if (synchronous) { - return this._textModel.$applyEdit(modelVersionId, edits, emitToExtHost, synchronous); - } else { - return new Promise(resolve => { - this._register(DOM.scheduleAtNextAnimationFrame(() => { - const ret = this._textModel.$applyEdit(modelVersionId, edits, emitToExtHost, true); - resolve(ret); - })); - }); - } - } - - async spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]) { - await this.notebookService.transformSpliceOutputs(this.textModel, splices); - this._textModel.$spliceNotebookCellOutputs(cellHandle, splices); - } - - handleEdit(editId: number, label: string | undefined): void { - this.undoRedoService.pushElement({ - type: UndoRedoElementType.Resource, - resource: this._textModel.uri, - label: label ?? nls.localize('defaultEditLabel', "Edit"), - undo: async () => { - await this._proxy.$undoNotebook(this._textModel.viewType, this._textModel.uri, editId, this._textModel.isDirty); - }, - redo: async () => { - await this._proxy.$redoNotebook(this._textModel.viewType, this._textModel.uri, editId, this._textModel.isDirty); - }, - }); - this._textModel.setDirty(true); - } - dispose() { - this._textModel.dispose(); + // this._textModel.dispose(); super.dispose(); } } @@ -203,21 +167,21 @@ class DocumentAndEditorState { @extHostNamedCustomer(MainContext.MainThreadNotebook) export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { - private readonly _notebookProviders = new Map(); + private readonly _notebookProviders = new Map(); private readonly _notebookKernels = new Map(); private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); private readonly _notebookRenderers = new Map(); private readonly _proxy: ExtHostNotebookShape; private _toDisposeOnEditorRemove = new Map(); private _currentState?: DocumentAndEditorState; + private _editorEventListenersMapping: Map = new Map(); constructor( extHostContext: IExtHostContext, @INotebookService private _notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { super(); @@ -226,15 +190,23 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { - let controller = this._notebookProviders.get(viewType); - - if (controller) { - return controller.tryApplyEdits(resource, modelVersionId, edits, renderers); + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + if (textModel) { + await this._notebookService.transformEditsOutputs(textModel, edits); + return textModel.$applyEdit(modelVersionId, edits, true); } return false; } + async removeNotebookTextModel(uri: URI): Promise { + // TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together + await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] }); + let textModelDisposableStore = this._editorEventListenersMapping.get(uri.toString()); + textModelDisposableStore?.dispose(); + this._editorEventListenersMapping.delete(URI.from(uri).toString()); + } + private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta) { if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) { return false; @@ -300,14 +272,39 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._removeNotebookEditor(editors); })); - this._register(this._notebookService.onNotebookDocumentAdd(() => { + this._register(this._notebookService.onNotebookDocumentAdd((documents) => { + documents.forEach(doc => { + if (!this._editorEventListenersMapping.has(doc.toString())) { + const disposableStore = new DisposableStore(); + const textModel = this._notebookService.getNotebookTextModel(doc); + disposableStore.add(textModel!.onDidModelChangeProxy(e => { + this._proxy.$acceptModelChanged(textModel!.uri, e); + this._proxy.$acceptEditorPropertiesChanged(doc, { selections: { selections: textModel!.selections }, metadata: null }); + })); + disposableStore.add(textModel!.onDidSelectionChange(e => { + const selectionsChange = e ? { selections: e } : null; + this._proxy.$acceptEditorPropertiesChanged(doc, { selections: selectionsChange, metadata: null }); + })); + + this._editorEventListenersMapping.set(textModel!.uri.toString(), disposableStore); + } + }); this._updateState(); })); - this._register(this._notebookService.onNotebookDocumentRemove(() => { + this._register(this._notebookService.onNotebookDocumentRemove((documents) => { + documents.forEach(doc => { + this._editorEventListenersMapping.get(doc.toString())?.dispose(); + this._editorEventListenersMapping.delete(doc.toString()); + }); + this._updateState(); })); + this._register(this._notebookService.onDidChangeNotebookActiveKernel(e => { + this._proxy.$acceptNotebookActiveKernelChange(e); + })); + const updateOrder = () => { let userOrder = this.configurationService.getValue('notebook.displayOrder'); this._proxy.$acceptDisplayOrder({ @@ -333,10 +330,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._updateState(notebookEditor); } - async addNotebookDocument(data: INotebookModelAddedData) { - this._updateState(); - } - private _addNotebookEditor(e: IEditor) { this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable( e.onDidChangeModel(() => this._updateState()), @@ -425,18 +418,92 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(id); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernel: INotebookKernelInfoDto | undefined): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType, supportBackup, kernel, this._notebookService, this._instantiationService); - this._notebookProviders.set(viewType, controller); - this._notebookService.registerNotebookController(viewType, extension, controller); + async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined): Promise { + const controller: IMainNotebookController = { + kernel: _kernel, + supportBackup: _supportBackup, + reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => { + const data = await this._proxy.$resolveNotebookData(_viewType, mainthreadTextModel.uri); + if (!data) { + return; + } + + mainthreadTextModel.languages = data.languages; + mainthreadTextModel.metadata = data.metadata; + + const edits: ICellEditOperation[] = [ + { editType: CellEditType.Delete, count: mainthreadTextModel.cells.length, index: 0 }, + { editType: CellEditType.Insert, index: 0, cells: data.cells } + ]; + + await this._notebookService.transformEditsOutputs(mainthreadTextModel, edits); + await new Promise(resolve => { + DOM.scheduleAtNextAnimationFrame(() => { + const ret = mainthreadTextModel!.$applyEdit(mainthreadTextModel!.versionId, edits, true); + resolve(ret); + }); + }); + }, + createNotebook: async (textModel: NotebookTextModel, backupId?: string) => { + // open notebook document + const data = await this._proxy.$resolveNotebookData(textModel.viewType, textModel.uri, backupId); + if (!data) { + return; + } + + textModel.languages = data.languages; + textModel.metadata = data.metadata; + + if (data.cells.length) { + textModel.initialize(data!.cells); + } else { + const mainCell = textModel.createCellTextModel([''], textModel.languages.length ? textModel.languages[0] : '', CellKind.Code, [], undefined); + textModel.insertTemplateCell(mainCell); + } + + this._proxy.$acceptEditorPropertiesChanged(textModel.uri, { selections: null, metadata: textModel.metadata }); + return; + }, + resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => { + await this._proxy.$resolveNotebookEditor(viewType, uri, editorId); + }, + executeNotebookByAttachedKernel: async (viewType: string, uri: URI) => { + return this.executeNotebookByAttachedKernel(viewType, uri); + }, + cancelNotebookByAttachedKernel: async (viewType: string, uri: URI) => { + return this.cancelNotebookByAttachedKernel(viewType, uri); + }, + onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => { + this._proxy.$onDidReceiveMessage(editorId, rendererType, message); + }, + removeNotebookDocument: async (uri: URI) => { + return this.removeNotebookTextModel(uri); + }, + executeNotebookCell: async (uri: URI, handle: number) => { + return this._proxy.$executeNotebookByAttachedKernel(_viewType, uri, handle); + }, + cancelNotebookCell: async (uri: URI, handle: number) => { + return this._proxy.$cancelNotebookByAttachedKernel(_viewType, uri, handle); + }, + save: async (uri: URI, token: CancellationToken) => { + return this._proxy.$saveNotebook(_viewType, uri, token); + }, + saveAs: async (uri: URI, target: URI, token: CancellationToken) => { + return this._proxy.$saveNotebookAs(_viewType, uri, target, token); + }, + backup: async (uri: URI, token: CancellationToken) => { + return this._proxy.$backup(_viewType, uri, token); + } + }; + + this._notebookProviders.set(_viewType, controller); + this._notebookService.registerNotebookController(_viewType, _extension, controller); return; } async $onNotebookChange(viewType: string, uri: UriComponents): Promise { - let controller = this._notebookProviders.get(viewType); - if (controller) { - controller.handleNotebookChange(uri); - } + const textModel = this._notebookService.getNotebookTextModel(URI.from(uri)); + textModel?.handleUnknownChange(); } async $unregisterNotebookProvider(viewType: string): Promise { @@ -462,16 +529,24 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const emitter = new Emitter(); const that = this; const provider = this._notebookService.registerNotebookKernelProvider({ + providerExtensionId: extension.id.value, + providerDescription: extension.description, onDidChangeKernels: emitter.event, selector: documentFilter, - provideKernels: (uri: URI, token: CancellationToken) => { - return that._proxy.$provideNotebookKernels(handle, uri, token); + provideKernels: async (uri: URI, token: CancellationToken) => { + const kernels = await that._proxy.$provideNotebookKernels(handle, uri, token); + return kernels.map(kernel => { + return { + ...kernel, + providerHandle: handle + }; + }); }, resolveKernel: (editorId: string, uri: URI, kernelId: string, token: CancellationToken) => { return that._proxy.$resolveNotebookKernel(handle, editorId, uri, kernelId, token); }, - executeNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined, token: CancellationToken) => { - return that._proxy.$executeNotebookKernelFromProvider(handle, uri, kernelId, cellHandle, token); + executeNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined) => { + return that._proxy.$executeNotebookKernelFromProvider(handle, uri, kernelId, cellHandle); } }); this._notebookKernelProviders.set(handle, { @@ -500,36 +575,35 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { - let controller = this._notebookProviders.get(viewType); - - if (controller) { - controller.updateLanguages(resource, languages); - } + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + textModel?.updateLanguages(languages); } async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise { - let controller = this._notebookProviders.get(viewType); - - if (controller) { - controller.updateNotebookMetadata(resource, metadata); - } + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + textModel?.updateNotebookMetadata(metadata); } async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise { - let controller = this._notebookProviders.get(viewType); - - if (controller) { - controller.updateNotebookCellMetadata(resource, handle, metadata); - } + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + textModel?.updateNotebookCellMetadata(handle, metadata); } async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { - let controller = this._notebookProviders.get(viewType); - await controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + + if (textModel) { + await this._notebookService.transformSpliceOutputs(textModel, splices); + textModel.$spliceNotebookCellOutputs(cellHandle, splices); + } } - async executeNotebookByAttachedKernel(viewType: string, uri: URI, token: CancellationToken): Promise { - return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, undefined, token); + async executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise { + return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, undefined); + } + + async cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise { + return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, undefined); } async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise { @@ -543,220 +617,20 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void { - let controller = this._notebookProviders.get(viewType); - controller?.handleEdit(resource, editId, label); + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + + if (textModel) { + textModel.$handleEdit(label, () => { + return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty); + }, () => { + return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty); + }); + } } $onContentChange(resource: UriComponents, viewType: string): void { - let controller = this._notebookProviders.get(viewType); - controller?.handleNotebookChange(resource); - } -} - -export class MainThreadNotebookController implements IMainNotebookController { - private _mapping: Map = new Map(); - static documentHandle: number = 0; - - constructor( - private readonly _proxy: ExtHostNotebookShape, - private _mainThreadNotebook: MainThreadNotebooks, - private _viewType: string, - private _supportBackup: boolean, - readonly kernel: INotebookKernelInfoDto | undefined, - readonly notebookService: INotebookService, - readonly _instantiationService: IInstantiationService - - ) { - } - - async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise { - let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); - - if (mainthreadNotebook) { - if (forceReload) { - const data = await this._proxy.$resolveNotebookData(viewType, uri); - if (!data) { - return; - } - - mainthreadNotebook.textModel.languages = data.languages; - mainthreadNotebook.textModel.metadata = data.metadata; - await mainthreadNotebook.applyEdit(mainthreadNotebook.textModel.versionId, [ - { editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 }, - { editType: CellEditType.Insert, index: 0, cells: data.cells } - ], true, false); - } - return mainthreadNotebook.textModel; - } - - let document = this._instantiationService.createInstance(MainThreadNotebookDocument, this._proxy, MainThreadNotebookController.documentHandle++, viewType, this._supportBackup, uri); - this._mapping.set(document.uri.toString(), document); - - if (backup) { - // trigger events - document.textModel.metadata = backup.metadata; - document.textModel.languages = backup.languages; - - // restored from backup, update the text model without emitting any event to exthost - await document.applyEdit(document.textModel.versionId, [ - { - editType: CellEditType.Insert, - index: 0, - cells: backup.cells || [] - } - ], false, true); - - // create document in ext host with cells data - await this._mainThreadNotebook.addNotebookDocument({ - viewType: document.viewType, - handle: document.handle, - uri: document.uri, - metadata: document.textModel.metadata, - versionId: document.textModel.versionId, - cells: document.textModel.cells.map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - })), - attachedEditor: editorId ? { - id: editorId, - selections: document.textModel.selections - } : undefined - }); - - return document.textModel; - } - - // open notebook document - const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId); - if (!data) { - return; - } - - document.textModel.languages = data.languages; - document.textModel.metadata = data.metadata; - - if (data.cells.length) { - document.textModel.initialize(data!.cells); - } else { - const mainCell = document.textModel.createCellTextModel([''], document.textModel.languages.length ? document.textModel.languages[0] : '', CellKind.Code, [], undefined); - document.textModel.insertTemplateCell(mainCell); - } - - await this._mainThreadNotebook.addNotebookDocument({ - viewType: document.viewType, - handle: document.handle, - uri: document.uri, - metadata: document.textModel.metadata, - versionId: document.textModel.versionId, - cells: document.textModel.cells.map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - })), - attachedEditor: editorId ? { - id: editorId, - selections: document.textModel.selections - } : undefined - }); - - this._proxy.$acceptEditorPropertiesChanged(uri, { selections: null, metadata: document.textModel.metadata }); - - return document.textModel; - } - - async resolveNotebookEditor(viewType: string, uri: URI, editorId: string) { - await this._proxy.$resolveNotebookEditor(viewType, uri, editorId); - } - - async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { - let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); - - if (mainthreadNotebook) { - return await mainthreadNotebook.applyEdit(modelVersionId, edits, true, true); - } - - return false; - } - - async spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { - let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); - await mainthreadNotebook?.spliceNotebookCellOutputs(cellHandle, splices); - } - - async executeNotebookByAttachedKernel(viewType: string, uri: URI, token: CancellationToken): Promise { - return this._mainThreadNotebook.executeNotebookByAttachedKernel(viewType, uri, token); - } - - onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: unknown): void { - this._proxy.$onDidReceiveMessage(editorId, rendererType, message); - } - - async removeNotebookDocument(notebook: INotebookTextModel): Promise { - let document = this._mapping.get(URI.from(notebook.uri).toString()); - - if (!document) { - return; - } - - // TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together - await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] }); - document.dispose(); - this._mapping.delete(URI.from(notebook.uri).toString()); - } - - // Methods for ExtHost - - handleNotebookChange(resource: UriComponents) { - let document = this._mapping.get(URI.from(resource).toString()); - document?.textModel.handleUnknownChange(); - } - - handleEdit(resource: UriComponents, editId: number, label: string | undefined): void { - let document = this._mapping.get(URI.from(resource).toString()); - document?.handleEdit(editId, label); - } - - updateLanguages(resource: UriComponents, languages: string[]) { - let document = this._mapping.get(URI.from(resource).toString()); - document?.textModel.updateLanguages(languages); - } - - updateNotebookMetadata(resource: UriComponents, metadata: NotebookDocumentMetadata) { - let document = this._mapping.get(URI.from(resource).toString()); - document?.textModel.updateNotebookMetadata(metadata); - } - - updateNotebookCellMetadata(resource: UriComponents, handle: number, metadata: NotebookCellMetadata) { - let document = this._mapping.get(URI.from(resource).toString()); - document?.textModel.updateNotebookCellMetadata(handle, metadata); - } - - async executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise { - return this._proxy.$executeNotebookByAttachedKernel(this._viewType, uri, handle, token); - } - - async save(uri: URI, token: CancellationToken): Promise { - return this._proxy.$saveNotebook(this._viewType, uri, token); - } - - async saveAs(uri: URI, target: URI, token: CancellationToken): Promise { - return this._proxy.$saveNotebookAs(this._viewType, uri, target, token); - } - - async backup(uri: URI, token: CancellationToken): Promise { - const backupId = await this._proxy.$backup(this._viewType, uri, token); - return backupId; + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + textModel?.handleUnknownChange(); } } @@ -772,8 +646,8 @@ export class MainThreadNotebookKernel implements INotebookKernelInfo { ) { } - async executeNotebook(viewType: string, uri: URI, handle: number | undefined, token: CancellationToken): Promise { - return this._proxy.$executeNotebook2(this.id, viewType, uri, handle, token); + async executeNotebook(viewType: string, uri: URI, handle: number | undefined): Promise { + return this._proxy.$executeNotebook2(this.id, viewType, uri, handle); } } diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index a779de27b58..e4166f6df8f 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -613,6 +613,9 @@ export class MainThreadTask implements MainThreadTaskShape { public $registerTaskSystem(key: string, info: TaskSystemInfoDTO): void { let platform: Platform.Platform; switch (info.platform) { + case 'Web': + platform = Platform.Platform.Web; + break; case 'win32': platform = Platform.Platform.Windows; break; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b343f44f2d8..036366842bf 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -933,10 +933,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeVisibleNotebookEditors; }, - get activeNotebookKernel() { - checkProposedApiEnabled(extension); - return extHostNotebook.activeNotebookKernel; - }, get onDidChangeActiveNotebookKernel() { checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeActiveNotebookKernel; @@ -1112,7 +1108,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TimelineItem: extHostTypes.TimelineItem, CellKind: extHostTypes.CellKind, CellOutputKind: extHostTypes.CellOutputKind, - NotebookCellRunState: extHostTypes.NotebookCellRunState + NotebookCellRunState: extHostTypes.NotebookCellRunState, + NotebookRunState: extHostTypes.NotebookRunState }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b004b04eb5f..e278c674cb5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -165,7 +165,7 @@ export interface MainThreadAuthenticationShape extends IDisposable { $selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise; $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise; $loginPrompt(providerName: string, extensionName: string): Promise; - $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise; + $setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise; $requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise; $getSessions(providerId: string): Promise>; @@ -597,6 +597,7 @@ export interface WebviewExtensionDescription { export interface NotebookExtensionDescription { readonly id: ExtensionIdentifier; readonly location: UriComponents; + readonly description?: string; } export enum WebviewEditorCapabilities { @@ -1616,13 +1617,15 @@ export interface ExtHostNotebookShape { $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise; $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; - $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; - $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined, token: CancellationToken): Promise; - $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; + $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; + $cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; + $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; + $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; + $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void; $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; $renderOutputs2(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 9ade3fff5fb..d1c44e7d4e2 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -37,7 +37,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } get providers(): ReadonlyArray { - return Object.freeze(this._providers); + return Object.freeze(this._providers.slice()); } async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions & { createIfNone: true }): Promise; @@ -75,7 +75,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } const session = await provider.login(scopes); - await this._proxy.$setTrustedExtension(providerId, session.account.label, extensionId, extensionName); + await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id); return session; } else { await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName); diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index c55f9218edc..9d3cb5a628d 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -16,7 +16,7 @@ import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { CellEditType, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput, INotebookKernelInfoDto2, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { NotImplementedProxy } from 'vs/base/common/types'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; @@ -617,6 +617,16 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook this._active = value; } + private _kernel?: vscode.NotebookKernel; + + get kernel() { + return this._kernel; + } + + set kernel(_kernel: vscode.NotebookKernel | undefined) { + throw readonly('kernel'); + } + private _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; private _onDidReceiveMessage = new Emitter(); @@ -692,6 +702,9 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook throw readonly('viewColumn'); } + updateActiveKernel(kernel?: vscode.NotebookKernel) { + this._kernel = kernel; + } async postMessage(message: any): Promise { return this._webComm.postMessage(message); } @@ -767,11 +780,18 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { const data = await this._provider.provideKernels(document, token) || []; const newMap = new Map(); + let kernel_unique_pool = 0; + let kernelIdCache = new Set(); const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => { let id = this._kernelToId.get(kernel); if (id === undefined) { - id = UUID.generateUuid(); + if (kernel.id && kernelIdCache.has(kernel.id)) { + id = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`; + } else { + id = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`; + } + this._kernelToId.set(kernel, id); } @@ -798,6 +818,10 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { return transformedData; } + getKernel(kernelId: string) { + return this._idToKernel.get(kernelId); + } + async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) { const kernel = this._idToKernel.get(kernelId); @@ -806,7 +830,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { } } - async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined, token: CancellationToken) { + async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { const kernel = this._idToKernel.get(kernelId); if (!kernel) { @@ -814,11 +838,35 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { } if (cell) { - return kernel.executeCell(document, cell, token); + return withToken(token => (kernel.executeCell as any)(document, cell, token)); } else { - return kernel.executeAllCells(document, token); + return withToken(token => (kernel.executeAllCells as any)(document, token)); } } + + async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { + const kernel = this._idToKernel.get(kernelId); + + if (!kernel) { + return; + } + + if (cell) { + return kernel.cancelCellExecution(document, cell); + } else { + return kernel.cancelAllCellsExecution(document); + } + } +} + +// TODO@roblou remove 'token' passed to all execute APIs once extensions are updated +async function withToken(cb: (token: CancellationToken) => any) { + const source = new CancellationTokenSource(); + try { + await cb(source.token); + } finally { + source.dispose(); + } } export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler { @@ -864,9 +912,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _onDidCloseNotebookDocument = new Emitter(); onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; visibleNotebookEditors: ExtHostNotebookEditor[] = []; - activeNotebookKernel?: vscode.NotebookKernel; - - private _onDidChangeActiveNotebookKernel = new Emitter(); + private _onDidChangeActiveNotebookKernel = new Emitter<{ document: ExtHostNotebookDocument, kernel: vscode.NotebookKernel | undefined }>(); onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event; private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; @@ -912,7 +958,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer); this._notebookOutputRenderers.set(extHostRenderer.type, extHostRenderer); - this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, renderer.preloads || []); + this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, type, filter, renderer.preloads || []); return new extHostTypes.Disposable(() => { this._notebookOutputRenderers.delete(extHostRenderer.type); this._proxy.$unregisterNotebookRenderer(extHostRenderer.type); @@ -1037,7 +1083,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const supportBackup = !!provider.backupNotebook; - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined); return new extHostTypes.Disposable(() => { listener.dispose(); @@ -1050,7 +1096,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++; const adapter = new ExtHostNotebookKernelProviderAdapter(this._proxy, handle, extension, provider); this._notebookKernelProviders.set(handle, adapter); - this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation }, handle, { + this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, { viewType: selector.viewType, filenamePattern: selector.filenamePattern ? typeConverters.GlobPattern.from(selector.filenamePattern) : undefined, excludeFileNamePattern: selector.excludeFileNamePattern ? typeConverters.GlobPattern.from(selector.excludeFileNamePattern) : undefined, @@ -1103,7 +1149,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._notebookKernels.set(id, { kernel, extension }); const transformedSelectors = selectors.map(selector => typeConverters.GlobPattern.from(selector)); - this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation }, id, kernel.label, transformedSelectors, kernel.preloads || []); + this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, id, kernel.label, transformedSelectors, kernel.preloads || []); return new extHostTypes.Disposable(() => { this._notebookKernels.delete(id); this._proxy.$unregisterNotebookKernel(id); @@ -1198,7 +1244,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } - async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { + async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { let document = this._documents.get(URI.revive(uri).toString()); if (!document) { @@ -1211,23 +1257,44 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (provider.kernel) { if (cell) { - return provider.kernel.executeCell(document, cell, token); + return withToken(token => (provider.kernel!.executeCell as any)(document, cell, token)); } else { - return provider.kernel.executeAllCells(document, token); + return withToken(token => (provider.kernel!.executeAllCells as any)(document, token)); } } } } - async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined, token: CancellationToken): Promise { + async $cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { + const document = this._documents.get(URI.revive(uri).toString()); + + if (!document) { + return; + } + + if (this._notebookContentProviders.has(viewType)) { + const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; + const provider = this._notebookContentProviders.get(viewType)!.provider; + + if (provider.kernel) { + if (cell) { + return provider.kernel.cancelCellExecution(document, cell); + } else { + return provider.kernel.cancelAllCellsExecution(document); + } + } + } + } + + async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise { await this._withAdapter(handle, uri, async (adapter, document) => { let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; - return adapter.executeNotebook(kernelId, document, cell, token); + return adapter.executeNotebook(kernelId, document, cell); }); } - async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { + async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { let document = this._documents.get(URI.revive(uri).toString()); if (!document || document.viewType !== viewType) { @@ -1243,9 +1310,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; if (cell) { - return kernelInfo.kernel.executeCell(document, cell, token); + return withToken(token => (kernelInfo!.kernel.executeCell as any)(document, cell, token)); } else { - return kernelInfo.kernel.executeAllCells(document, token); + return withToken(token => (kernelInfo!.kernel.executeAllCells as any)(document, token)); } } @@ -1314,6 +1381,20 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._outputDisplayOrder = displayOrder; } + $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }) { + if (event.providerHandle !== undefined) { + this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => { + const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined; + this._editors.forEach(editor => { + if (editor.editor.document === document) { + editor.editor.updateActiveKernel(kernel); + } + }); + this._onDidChangeActiveNotebookKernel.fire({ document, kernel }); + }); + } + } + // TODO: remove document - editor one on one mapping private _getEditorFromURI(uriComponents: UriComponents) { const uriStr = URI.revive(uriComponents).toString(); diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 066e6ddb489..785685a2679 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -696,13 +696,11 @@ export class WorkerExtHostTask extends ExtHostTaskBase { @IExtHostApiDeprecationService deprecationService: IExtHostApiDeprecationService ) { super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService, deprecationService); - if (initData.remote.isRemote && initData.remote.authority) { - this.registerTaskSystem(Schemas.vscodeRemote, { - scheme: Schemas.vscodeRemote, - authority: initData.remote.authority, - platform: Platform.PlatformToString(Platform.Platform.Web) - }); - } + this.registerTaskSystem(Schemas.vscodeRemote, { + scheme: Schemas.vscodeRemote, + authority: '', + platform: Platform.PlatformToString(Platform.Platform.Web) + }); } public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 1de7259f01b..447fa8092c6 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2739,6 +2739,11 @@ export enum NotebookCellRunState { Error = 4 } +export enum NotebookRunState { + Running = 1, + Idle = 2 +} + //#endregion //#region Timeline diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 21f87bbc961..5b7acb9c747 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -51,7 +51,7 @@ namespace schema { case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu; case 'scm/title': return MenuId.SCMTitle; case 'scm/sourceControl': return MenuId.SCMSourceControl; - case 'scm/resourceState/context': return MenuId.SCMResourceContext;// + case 'scm/resourceState/context': return MenuId.SCMResourceContext; case 'scm/resourceFolder/context': return MenuId.SCMResourceFolderContext; case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; case 'scm/change/title': return MenuId.SCMChangeContext;// diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 619ceaf8ede..9ea421e1256 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -12,10 +12,13 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { Direction } from 'vs/base/browser/ui/grid/grid'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; abstract class BaseNavigationAction extends Action { @@ -257,12 +260,38 @@ export class FocusPreviousPart extends Action { } } -const registry = Registry.as(Extensions.WorkbenchActions); +class GoHomeContributor implements IWorkbenchContribution { + + constructor( + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + ) { + const homeIndicator = environmentService.options?.homeIndicator; + if (homeIndicator) { + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.goHome`, + title: nls.localize('goHome', "Go Home"), + menu: { id: MenuId.MenubarWebNavigationMenu } + }); + } + async run(): Promise { + window.location.href = homeIndicator.href; + } + }); + } + } +} + +const actionsRegistry = Registry.as(Extensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateUpAction, undefined), 'View: Navigate to the View Above', viewCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateDownAction, undefined), 'View: Navigate to the View Below', viewCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateLeftAction, undefined), 'View: Navigate to the View on the Left', viewCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateRightAction, undefined), 'View: Navigate to the View on the Right', viewCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusNextPart, { primary: KeyCode.F6 }), 'View: Focus Next Part', viewCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousPart, { primary: KeyMod.Shift | KeyCode.F6 }), 'View: Focus Previous Part', viewCategory); +actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateUpAction, undefined), 'View: Navigate to the View Above', viewCategory); +actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateDownAction, undefined), 'View: Navigate to the View Below', viewCategory); +actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateLeftAction, undefined), 'View: Navigate to the View on the Left', viewCategory); +actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateRightAction, undefined), 'View: Navigate to the View on the Right', viewCategory); +actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusNextPart, { primary: KeyCode.F6 }), 'View: Focus Next Part', viewCategory); +actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousPart, { primary: KeyMod.Shift | KeyCode.F6 }), 'View: Focus Previous Part', viewCategory); + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(GoHomeContributor, LifecyclePhase.Ready); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index b961c0fe0fe..14dca63bc68 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -32,7 +32,8 @@ import { Selection } from 'vs/editor/common/core/selection'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 18196e719a3..88e1c72abb7 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -22,7 +22,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio enum: ['default', 'large'], enumDescriptions: [ nls.localize('workbench.editor.titleScrollbarSizing.default', "The default size."), - nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabed more easily with the mouse") + nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabbed more easily with the mouse") ], description: nls.localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."), default: 'default', diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 3461cc5eb0a..71f86a19285 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -34,7 +34,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService'; -import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; +import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding'; import { Schemas } from 'vs/base/common/network'; import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWatcher'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 9bf3ad060f3..8e0e190bf45 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -284,7 +284,7 @@ suite('Files - FileEditorInput', () => { } const inputWithPreferredResourceDeserialized = factory.deserialize(instantiationService, inputWithPreferredResourceSerialized) as FileEditorInput; - assert.equal(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.resource.toString()); + assert.equal(inputWithPreferredResource.resource.toString(), inputWithPreferredResourceDeserialized.resource.toString()); assert.equal(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.preferredResource.toString()); }); }); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index 439a4ba8eea..8799eee4c2d 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -23,7 +23,7 @@ const workbenchActionsRegistry = Registry.as(Extension if (!!product.reportIssueUrl) { workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ReportPerformanceIssueUsingReporterAction), 'Help: Report Performance Issue', helpCategory.value); - const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); + const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."); CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string] | OpenIssueReporterArgs) { const data: Partial = Array.isArray(args) diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index 0f06c778c4e..92dd4581ccc 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -15,6 +15,8 @@ import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-brow import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { platform } from 'process'; +import { IProductService } from 'vs/platform/product/common/productService'; export class WorkbenchIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -24,7 +26,8 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IThemeService private readonly themeService: IThemeService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService ) { } async openReporter(dataOverrides: Partial = {}): Promise { @@ -67,7 +70,9 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { hoverBackground: getColor(theme, listHoverBackground), hoverForeground: getColor(theme, listHoverForeground), highlightForeground: getColor(theme, listHighlightForeground), - } + }, + platform, + applicationName: this.productService.applicationName }; return this.issueService.openProcessExplorer(data); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 462c1a73fcd..6a235155596 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -1319,11 +1319,9 @@ registerAction2(class extends NotebookAction { }); async function splitCell(context: INotebookCellActionContext): Promise { - if (context.cell.cellKind === CellKind.Code) { - const newCells = await context.notebookEditor.splitNotebookCell(context.cell); - if (newCells) { - context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], 'editor'); - } + const newCells = await context.notebookEditor.splitNotebookCell(context.cell); + if (newCells) { + context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], 'editor'); } } @@ -1335,13 +1333,13 @@ registerAction2(class extends NotebookCellAction { title: localize('notebookActions.splitCell', "Split Cell"), menu: { id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_TYPE.isEqualTo('code'), NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, InputFocusedContext), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, InputFocusedContext), order: CellToolbarOrder.SplitCell, group: CELL_TITLE_GROUP_ID }, icon: { id: 'codicon/split-vertical' }, keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_TYPE.isEqualTo('code'), NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, InputFocusedContext), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, InputFocusedContext), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH), weight: KeybindingWeight.WorkbenchContrib }, @@ -1355,7 +1353,7 @@ registerAction2(class extends NotebookCellAction { async function joinCells(context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { - const cell = await context.notebookEditor.joinNotebookCells(context.cell, direction, CellKind.Code); + const cell = await context.notebookEditor.joinNotebookCells(context.cell, direction); if (cell) { context.notebookEditor.focusNotebookCell(cell, 'editor'); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index e3bedbffbcd..552e93395bf 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -19,6 +19,8 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWo import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; +import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; registerAction2(class extends Action2 { @@ -37,6 +39,7 @@ registerAction2(class extends Action2 { const editorService = accessor.get(IEditorService); const notebookService = accessor.get(INotebookService); const quickInputService = accessor.get(IQuickInputService); + const configurationService = accessor.get(IConfigurationService); const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; if (!activeEditorPane?.isNotebookEditor) { @@ -48,7 +51,7 @@ registerAction2(class extends Action2 { const tokenSource = new CancellationTokenSource(); const availableKernels2 = await notebookService.getContributedNotebookKernels2(editor.viewModel!.viewType, editor.viewModel!.uri, tokenSource.token); const availableKernels = notebookService.getContributedNotebookKernels(editor.viewModel!.viewType, editor.viewModel!.uri); - const picks: QuickPickInput[] = [...availableKernels2, ...availableKernels].map((a) => { + const picks: QuickPickInput[] = [...availableKernels2, ...availableKernels].map((a) => { return { id: a.id, label: a.label, @@ -59,12 +62,17 @@ registerAction2(class extends Action2 { : a.extension.value + (a.id === activeKernel?.id ? nls.localize('currentActiveKernel', " (Currently Active)") : ''), + kernelProviderId: a.extension.value, run: async () => { editor.activeKernel = a; if ((a as any).resolve) { (a as INotebookKernelInfo2).resolve(editor.uri!, editor.getId(), tokenSource.token); } - } + }, + buttons: [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel!.viewType) + }] }; }); @@ -78,16 +86,60 @@ registerAction2(class extends Action2 { description: activeKernel === undefined ? nls.localize('currentActiveBuiltinKernel', " (Currently Active)") : '', + kernelProviderId: provider.providerExtensionId, run: () => { editor.activeKernel = undefined; - } + }, + buttons: [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel!.viewType) + }] }); } - const action = await quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); - tokenSource.dispose(); - return action?.run(); + const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); + picker.items = picks; + picker.placeholder = nls.localize('pickAction', "Select Action"); + picker.matchOnDetail = true; + const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); + picker.dispose(); + }); + + picker.onDidTriggerItemButton(e => { + const pick = e.item; + const id = pick.id; + resolve(pick); // open the view + picker.dispose(); + + // And persist the setting + if (pick && id && pick.kernelProviderId) { + const newAssociation: NotebookKernelProviderAssociation = { viewType: editor.viewModel!.viewType, kernelProvider: pick.kernelProviderId }; + const currentAssociations = [...configurationService.getValue(notebookKernelProviderAssociationsSettingId)]; + + // First try updating existing association + for (let i = 0; i < currentAssociations.length; ++i) { + const existing = currentAssociations[i]; + if (existing.viewType === newAssociation.viewType) { + currentAssociations.splice(i, 1, newAssociation); + configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); + return; + } + } + + // Otherwise, create a new one + currentAssociations.unshift(newAssociation); + configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); + } + }); + + picker.show(); + }); + + tokenSource.dispose(); + return pickedItem?.run(); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 14a0da9f560..f65bbe5beeb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -8,7 +8,6 @@ import { IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; @@ -66,6 +65,13 @@ export interface NotebookLayoutChangeEvent { fontInfo?: boolean; } +export enum CodeCellLayoutState { + Uninitialized, + Estimated, + FromCache, + Measured +} + export interface CodeCellLayoutInfo { readonly fontInfo: BareFontInfo | null; readonly editorHeight: number; @@ -75,6 +81,7 @@ export interface CodeCellLayoutInfo { readonly outputTotalHeight: number; readonly indicatorHeight: number; readonly bottomToolbarOffset: number; + readonly layoutState: CodeCellLayoutState; } export interface CodeCellLayoutChangeEvent { @@ -109,7 +116,6 @@ export interface ICellViewModel { language: string; cellKind: CellKind; editState: CellEditState; - currentTokenSource: CancellationTokenSource | undefined; focusMode: CellFocusMode; getText(): string; getTextLength(): number; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index c9afd46a5ea..cb56c3475d7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -37,7 +37,7 @@ import { CellDragAndDropController, CodeCellRenderer, MarkdownCellRenderer, Note import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto, INotebookKernelInfo2, NotebookRunState, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -49,6 +49,8 @@ import { URI } from 'vs/base/common/uri'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; +import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; +import { notebookKernelProviderAssociationsSettingId, NotebookKernelProviderAssociations } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; const $ = DOM.$; @@ -90,7 +92,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _outputFocus: IContextKey | null = null; private _editorEditable: IContextKey | null = null; private _editorRunnable: IContextKey | null = null; - private _editorExecutingNotebook: IContextKey | null = null; + private _notebookExecuting: IContextKey | null = null; private _notebookHasMultipleKernels: IContextKey | null = null; private _outputRenderer: OutputRenderer; protected readonly _contributions: { [key: string]: INotebookEditorContribution; }; @@ -270,7 +272,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._editorEditable.set(true); this._editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(this.contextKeyService); this._editorRunnable.set(true); - this._editorExecutingNotebook = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService); + this._notebookExecuting = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService); this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(this.contextKeyService); this._notebookHasMultipleKernels.set(false); @@ -403,7 +405,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._cellListFocusTracker = this._register(DOM.trackFocus(this._body)); this._register(this._cellListFocusTracker.onDidFocus(() => { - // hack - FocusTracker forces 'blur' to run after 'focus'. + // hack - FocusTracker forces 'blur' to run after 'focus'. // We want the other way around so that when switching from notebook to notebook, the focus happens last setTimeout(() => { this.updateEditorFocus(); @@ -484,14 +486,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._currentKernelTokenSource = new CancellationTokenSource(); this._localStore.add(this._currentKernelTokenSource); - await this._setKernels(textModel, this._currentKernelTokenSource); + // we don't await for it, otherwise it will slow down the file opening + this._setKernels(textModel, this._currentKernelTokenSource); this._localStore.add(this.notebookService.onDidChangeKernels(async () => { - if (this.activeKernel === undefined) { - this._currentKernelTokenSource?.cancel(); - this._currentKernelTokenSource = new CancellationTokenSource(); - await this._setKernels(textModel, this._currentKernelTokenSource); - } + this._currentKernelTokenSource?.cancel(); + this._currentKernelTokenSource = new CancellationTokenSource(); + await this._setKernels(textModel, this._currentKernelTokenSource); })); this._localStore.add(this._list!.onDidChangeFocus(() => { @@ -565,8 +566,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _setKernels(textModel: NotebookTextModel, tokenSource: CancellationTokenSource) { const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; const availableKernels2 = await this.notebookService.getContributedNotebookKernels2(textModel.viewType, textModel.uri, tokenSource.token); + + if (tokenSource.token.isCancellationRequested) { + return; + } + const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri); + if (tokenSource.token.isCancellationRequested) { + return; + } + if (provider.kernel && (availableKernels.length + availableKernels2.length) > 0) { this._notebookHasMultipleKernels!.set(true); this.multipleKernelsAvailable = true; @@ -578,27 +588,75 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.multipleKernelsAvailable = false; } + // @deprecated if (provider && provider.kernel) { // it has a builtin kernel, don't automatically choose a kernel this._loadKernelPreloads(provider.providerExtensionLocation, provider.kernel); + tokenSource.dispose(); return; } - // choose a preferred kernel - const kernelsFromSameExtension = availableKernels2.filter(kernel => kernel.extension.value === provider.providerId); - if (kernelsFromSameExtension.length) { - const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) || kernelsFromSameExtension[0]; - this.activeKernel = preferedKernel; - await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); + const activeKernelStillExist = [...availableKernels2, ...availableKernels].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined); + + if (activeKernelStillExist) { + // the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost return; } + if (availableKernels2.length) { + return this._setKernelsFromProviders(provider, availableKernels2, tokenSource); + } // the provider doesn't have a builtin kernel, choose a kernel this.activeKernel = availableKernels[0]; if (this.activeKernel) { this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); } + + tokenSource.dispose(); + } + + private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernelInfo2[], tokenSource: CancellationTokenSource) { + const rawAssociations = this.configurationService.getValue(notebookKernelProviderAssociationsSettingId) || []; + const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this.viewModel?.viewType)[0]?.kernelProvider; + + if (userSetKernelProvider) { + const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider); + + if (filteredKernels.length) { + this.activeKernel = filteredKernels.find(kernel => kernel.isPreferred) || filteredKernels[0]; + } else { + this.activeKernel = undefined; + } + + if (this.activeKernel) { + this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); + } + + tokenSource.dispose(); + return; + } + + // choose a preferred kernel + const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId); + if (kernelsFromSameExtension.length) { + const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) || kernelsFromSameExtension[0]; + this.activeKernel = preferedKernel; + this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); + tokenSource.dispose(); + return; + } + + // the provider doesn't have a builtin kernel, choose a kernel + this.activeKernel = kernels[0]; + if (this.activeKernel) { + this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); + } + + tokenSource.dispose(); } private _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernelInfoDto) { @@ -608,10 +666,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } private _updateForMetadata(): void { - this._editorEditable?.set(!!this.viewModel!.metadata?.editable); - this._editorRunnable?.set(!!this.viewModel!.metadata?.runnable); - DOM.toggleClass(this._overlayContainer, 'notebook-editor-editable', !!this.viewModel!.metadata?.editable); - DOM.toggleClass(this.getDomNode(), 'notebook-editor-editable', !!this.viewModel!.metadata?.editable); + const notebookMetadata = this.viewModel!.metadata; + this._editorEditable?.set(!!notebookMetadata?.editable); + this._editorRunnable?.set(!!notebookMetadata?.runnable); + DOM.toggleClass(this._overlayContainer, 'notebook-editor-editable', !!notebookMetadata?.editable); + DOM.toggleClass(this.getDomNode(), 'notebook-editor-editable', !!notebookMetadata?.editable); + + this._notebookExecuting?.set(notebookMetadata.runState === NotebookRunState.Running); } private async _createWebview(id: string, resource: URI): Promise { @@ -1129,14 +1190,26 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return undefined; } - cancelNotebookExecution(): void { - if (!this._notebookViewModel!.currentTokenSource) { - throw new Error('Notebook is not executing'); + async cancelNotebookExecution(): Promise { + if (this._notebookViewModel?.metadata.runState !== NotebookRunState.Running) { + return; } + return this._cancelNotebookExecution(); + } - this._notebookViewModel!.currentTokenSource.cancel(); - this._notebookViewModel!.currentTokenSource = undefined; + private async _cancelNotebookExecution(): Promise { + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + if (provider) { + const viewType = provider.id; + const notebookUri = this._notebookViewModel!.uri; + + if (this._activeKernel) { + await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, undefined); + } else if (provider.kernel) { + return await this.notebookService.cancelNotebook(viewType, notebookUri); + } + } } async executeNotebook(): Promise { @@ -1147,46 +1220,54 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._executeNotebook(); } - async _executeNotebook(): Promise { - if (this._notebookViewModel!.currentTokenSource) { - return; - } + private async _executeNotebook(): Promise { + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + if (provider) { + const viewType = provider.id; + const notebookUri = this._notebookViewModel!.uri; - const tokenSource = new CancellationTokenSource(); - try { - this._editorExecutingNotebook!.set(true); - this._notebookViewModel!.currentTokenSource = tokenSource; - const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - if (provider) { - const viewType = provider.id; - const notebookUri = this._notebookViewModel!.uri; - - if (this._activeKernel) { - // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support - if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) { - await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined, tokenSource.token); - } else { - await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id, tokenSource.token); - } - } else if (provider.kernel) { - return await this.notebookService.executeNotebook(viewType, notebookUri, tokenSource.token); + if (this._activeKernel) { + // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support + if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) { + await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined); + } else { + await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id); } + } else if (provider.kernel) { + return await this.notebookService.executeNotebook(viewType, notebookUri); } - - } finally { - this._editorExecutingNotebook!.set(false); - this._notebookViewModel!.currentTokenSource = undefined; - tokenSource.dispose(); } } - cancelNotebookCellExecution(cell: ICellViewModel): void { - if (!cell.currentTokenSource) { - throw new Error('Cell is not executing'); + async cancelNotebookCellExecution(cell: ICellViewModel): Promise { + if (cell.cellKind !== CellKind.Code) { + return; } - cell.currentTokenSource.cancel(); - cell.currentTokenSource = undefined; + const metadata = cell.getEvaluatedMetadata(this._notebookViewModel!.metadata); + if (!metadata.runnable) { + return; + } + + if (metadata.runState !== NotebookCellRunState.Running) { + return; + } + + await this._cancelNotebookCell(cell); + } + + private async _cancelNotebookCell(cell: ICellViewModel): Promise { + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + if (provider) { + const viewType = provider.id; + const notebookUri = this._notebookViewModel!.uri; + + if (this._activeKernel) { + return await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle); + } else if (provider.kernel) { + return await this.notebookService.cancelNotebookCell(viewType, notebookUri, cell.handle); + } + } } async executeNotebookCell(cell: ICellViewModel): Promise { @@ -1199,37 +1280,26 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - const tokenSource = new CancellationTokenSource(); - try { - await this._executeNotebookCell(cell, tokenSource); - } finally { - tokenSource.dispose(); - } + await this._executeNotebookCell(cell); } - private async _executeNotebookCell(cell: ICellViewModel, tokenSource: CancellationTokenSource): Promise { - try { - cell.currentTokenSource = tokenSource; + private async _executeNotebookCell(cell: ICellViewModel): Promise { + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + if (provider) { + const viewType = provider.id; + const notebookUri = this._notebookViewModel!.uri; - const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - if (provider) { - const viewType = provider.id; - const notebookUri = this._notebookViewModel!.uri; + if (this._activeKernel) { + // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support + if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) { + await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, cell.handle); + } else { - if (this._activeKernel) { - // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support - if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) { - await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, cell.handle, tokenSource.token); - } else { - - return await this.notebookService.executeNotebookCell2(viewType, notebookUri, cell.handle, this._activeKernel.id, tokenSource.token); - } - } else if (provider.kernel) { - return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, tokenSource.token); + return await this.notebookService.executeNotebookCell2(viewType, notebookUri, cell.handle, this._activeKernel.id); } + } else if (provider.kernel) { + return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle); } - } finally { - cell.currentTokenSource = undefined; } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts new file mode 100644 index 00000000000..c98c49112af --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelAssociation.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export class NotebookKernelProviderAssociationRegistry { + static extensionIds: (string | null)[] = []; + static extensionDescriptions: string[] = []; +} + +export class NotebookViewTypesExtensionRegistry { + static viewTypes: string[] = []; + static viewTypeDescriptions: string[] = []; +} + +export type NotebookKernelProviderAssociation = { + readonly viewType: string; + readonly kernelProvider?: string; +}; + +export type NotebookKernelProviderAssociations = readonly NotebookKernelProviderAssociation[]; + + +export const notebookKernelProviderAssociationsSettingId = 'notebook.kernelProviderAssociations'; + +export const viewTypeSchamaAddition: IJSONSchema = { + type: 'string', + enum: [] +}; + +export const notebookKernelProviderAssociationsConfigurationNode: IConfigurationNode = { + ...workbenchConfigurationNodeBase, + properties: { + [notebookKernelProviderAssociationsSettingId]: { + type: 'array', + markdownDescription: nls.localize('notebook.kernelProviderAssociations', "Defines a default kernel provider which takes precedence over all other kernel providers settings. Must be the identifier of an extension contributing a kernel provider."), + items: { + type: 'object', + defaultSnippets: [{ + body: { + 'viewType': '$1', + 'kernelProvider': '$2' + } + }], + properties: { + 'viewType': { + type: ['string', 'null'], + default: null, + enum: NotebookViewTypesExtensionRegistry.viewTypes, + markdownEnumDescriptions: NotebookViewTypesExtensionRegistry.viewTypeDescriptions + }, + 'kernelProvider': { + type: ['string', 'null'], + default: null, + enum: NotebookKernelProviderAssociationRegistry.extensionIds, + markdownEnumDescriptions: NotebookKernelProviderAssociationRegistry.extensionDescriptions + } + } + } + } + } +}; + +export function updateNotebookKernelProvideAssociationSchema(): void { + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(notebookKernelProviderAssociationsConfigurationNode); +} + +Registry.as(Extensions.Configuration) + .registerConfiguration(notebookKernelProviderAssociationsConfigurationNode); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index c4db8ed24b9..d4bce90bc8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -10,7 +10,7 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint, INotebo import { NotebookProviderInfo, NotebookEditorDescriptor } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, CellOutputKind, ITransformedDisplayOutputDto, IDisplayOutput, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, IOrderedMimeType, mimeTypeSupportedByCore, IOutputRenderRequestOutputInfo, IOutputRenderRequestCellInfo, NotebookCellOutputsSplice, ICellEditOperation, CellEditType, ICellInsertEdit, IOutputRenderResponse, IProcessedOutput, BUILTIN_RENDERER_ID, NotebookEditorPriority, INotebookKernelProvider, notebookDocumentFilterMatch, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, CellOutputKind, ITransformedDisplayOutputDto, IDisplayOutput, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, IOrderedMimeType, mimeTypeSupportedByCore, IOutputRenderRequestOutputInfo, IOutputRenderRequestCellInfo, NotebookCellOutputsSplice, ICellEditOperation, CellEditType, ICellInsertEdit, IOutputRenderResponse, IProcessedOutput, BUILTIN_RENDERER_ID, NotebookEditorPriority, INotebookKernelProvider, notebookDocumentFilterMatch, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { Iterable } from 'vs/base/common/iterator'; @@ -29,11 +29,51 @@ import { StorageScope, IStorageService } from 'vs/platform/storage/common/storag import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { generateUuid } from 'vs/base/common/uuid'; import { flatten } from 'vs/base/common/arrays'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { NotebookKernelProviderAssociationRegistry, updateNotebookKernelProvideAssociationSchema, NotebookViewTypesExtensionRegistry } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; function MODEL_ID(resource: URI): string { return resource.toString(); } +export class NotebookKernelProviderInfoStore extends Disposable { + private readonly _notebookKernelProviders: INotebookKernelProvider[] = []; + + constructor() { + super(); + } + + add(provider: INotebookKernelProvider) { + this._notebookKernelProviders.push(provider); + this._updateProviderExtensionsInfo(); + + return toDisposable(() => { + let idx = this._notebookKernelProviders.indexOf(provider); + if (idx >= 0) { + this._notebookKernelProviders.splice(idx, 1); + } + + this._updateProviderExtensionsInfo(); + }); + } + + get(viewType: string, resource: URI) { + return this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource)); + } + + private _updateProviderExtensionsInfo() { + NotebookKernelProviderAssociationRegistry.extensionIds.length = 0; + NotebookKernelProviderAssociationRegistry.extensionDescriptions.length = 0; + + this._notebookKernelProviders.forEach(provider => { + NotebookKernelProviderAssociationRegistry.extensionIds.push(provider.providerExtensionId); + NotebookKernelProviderAssociationRegistry.extensionDescriptions.push(provider.providerDescription || ''); + }); + + updateNotebookKernelProvideAssociationSchema(); + } +} + export class NotebookProviderInfoStore extends Disposable { private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors'; private static readonly CUSTOM_EDITORS_ENTRY_ID = 'editors'; @@ -53,6 +93,8 @@ export class NotebookProviderInfoStore extends Disposable { this.add(new NotebookProviderInfo(info)); } + this._updateProviderExtensionsInfo(); + this._register(extensionService.onDidRegisterExtensions(() => { if (!this._handled) { // there is no extension point registered for notebook content provider @@ -60,6 +102,8 @@ export class NotebookProviderInfoStore extends Disposable { this.clear(); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = []; this._memento.saveMemento(); + + this._updateProviderExtensionsInfo(); } })); } @@ -75,7 +119,8 @@ export class NotebookProviderInfoStore extends Disposable { displayName: notebookContribution.displayName, selector: notebookContribution.selector || [], priority: this._convertPriority(notebookContribution.priority), - providerId: extension.description.identifier.value, + providerExtensionId: extension.description.identifier.value, + providerDescription: extension.description.description, providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, providerExtensionLocation: extension.description.extensionLocation })); @@ -85,6 +130,22 @@ export class NotebookProviderInfoStore extends Disposable { const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); this._memento.saveMemento(); + + this._updateProviderExtensionsInfo(); + } + + private _updateProviderExtensionsInfo() { + NotebookViewTypesExtensionRegistry.viewTypes.length = 0; + NotebookViewTypesExtensionRegistry.viewTypeDescriptions.length = 0; + + for (const contribute of this._contributedEditors) { + if (contribute[1].providerExtensionId) { + NotebookViewTypesExtensionRegistry.viewTypes.push(contribute[1].id); + NotebookViewTypesExtensionRegistry.viewTypeDescriptions.push(`${contribute[1].displayName}`); + } + } + + updateNotebookKernelProvideAssociationSchema(); } private _convertPriority(priority?: string) { @@ -166,14 +227,17 @@ class ModelData implements IDisposable { } export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { declare readonly _serviceBrand: undefined; + static mainthreadNotebookDocumentHandle: number = 0; private readonly _notebookProviders = new Map(); private readonly _notebookRenderers = new Map(); private readonly _notebookKernels = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore; notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); + notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore(); private readonly _models = new Map(); private _onDidChangeActiveEditor = new Emitter(); onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; + private _activeEditorDisposables = new DisposableStore(); private _onDidChangeVisibleEditors = new Emitter(); onDidChangeVisibleEditors: Event = this._onDidChangeVisibleEditors.event; private readonly _onNotebookEditorAdd: Emitter = this._register(new Emitter()); @@ -191,18 +255,20 @@ export class NotebookService extends Disposable implements INotebookService, ICu private readonly _onDidChangeKernels = new Emitter(); onDidChangeKernels: Event = this._onDidChangeKernels.event; + private readonly _onDidChangeNotebookActiveKernel = new Emitter<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }>(); + onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }> = this._onDidChangeNotebookActiveKernel.event; private cutItems: NotebookCellTextModel[] | undefined; private _lastClipboardIsCopy: boolean = true; private _displayOrder: { userOrder: string[], defaultOrder: string[] } = Object.create(null); - private readonly _notebookKernelProviders: INotebookKernelProvider[] = []; constructor( @IExtensionService private readonly _extensionService: IExtensionService, @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IStorageService private readonly _storageService: IStorageService + @IStorageService private readonly _storageService: IStorageService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); @@ -301,21 +367,18 @@ export class NotebookService extends Disposable implements INotebookService, ICu } registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { - this._notebookKernelProviders.push(provider); + const d = this.notebookKernelProviderInfoStore.add(provider); const kernelChangeEventListener = provider.onDidChangeKernels(() => { this._onDidChangeKernels.fire(); }); return toDisposable(() => { kernelChangeEventListener.dispose(); - let idx = this._notebookKernelProviders.indexOf(provider); - if (idx >= 0) { - this._notebookKernelProviders.splice(idx, 1); - } + d.dispose(); }); } async getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise { - const filteredProvider = this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource)); + const filteredProvider = this.notebookKernelProviderInfoStore.get(viewType, resource); const result = new Array(filteredProvider.length); const promises = filteredProvider.map(async (provider, index) => { @@ -332,8 +395,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu resolve: async (uri: URI, editorId: string, token: CancellationToken) => { return provider.resolveKernel(editorId, uri, dto.id, token); }, - executeNotebookCell: async (uri: URI, handle: number | undefined, token: CancellationToken) => { - return provider.executeNotebook(uri, dto.id, handle, token); + executeNotebookCell: async (uri: URI, handle: number | undefined) => { + return provider.executeNotebook(uri, dto.id, handle); } }; }); @@ -390,43 +453,33 @@ export class NotebookService extends Disposable implements INotebookService, ICu return renderer; } - async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise { - const provider = this._notebookProviders.get(viewType); - if (!provider) { - return undefined; - } - - const notebookModel = await provider.controller.createNotebook(viewType, uri, { metadata, languages, cells }, false, editorId); - if (!notebookModel) { - return undefined; - } - - // new notebook model created - const modelId = MODEL_ID(uri); - const modelData = new ModelData( - notebookModel, - (model) => this._onWillDisposeDocument(model), - ); - this._models.set(modelId, modelData); - this._onNotebookDocumentAdd.fire([notebookModel.uri]); - // after the document is added to the store and sent to ext host, we transform the ouputs - await this.transformTextModelOutputs(notebookModel!); - return modelData.model; - } - async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; } - const notebookModel = await provider.controller.createNotebook(viewType, uri, undefined, forceReload, editorId, backupId); - if (!notebookModel) { - return undefined; + const modelId = MODEL_ID(uri); + + let notebookModel: NotebookTextModel | undefined = undefined; + if (this._models.has(modelId)) { + // the model already exists + notebookModel = this._models.get(modelId)!.model; + if (forceReload) { + await provider.controller.reloadNotebook(notebookModel); + } + + return notebookModel; + } else { + notebookModel = this._instantiationService.createInstance(NotebookTextModel, NotebookService.mainthreadNotebookDocumentHandle++, viewType, provider.controller.supportBackup, uri); + await provider.controller.createNotebook(notebookModel, backupId); + + if (!notebookModel) { + return undefined; + } } // new notebook model created - const modelId = MODEL_ID(uri); const modelData = new ModelData( notebookModel!, (model) => this._onWillDisposeDocument(model), @@ -444,6 +497,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu return modelData.model; } + getNotebookTextModel(uri: URI): NotebookTextModel | undefined { + const modelId = MODEL_ID(uri); + + return this._models.get(modelId)?.model; + } + private async _fillInTransformedOutputs( renderers: Set, requestItems: IOutputRenderRequestCellInfo[], @@ -677,34 +736,51 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } - async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise { + async executeNotebook(viewType: string, uri: URI): Promise { let provider = this._notebookProviders.get(viewType); if (provider) { - return provider.controller.executeNotebookByAttachedKernel(viewType, uri, token); + return provider.controller.executeNotebookByAttachedKernel(viewType, uri); } return; } - async executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise { + async executeNotebookCell(viewType: string, uri: URI, handle: number): Promise { const provider = this._notebookProviders.get(viewType); if (provider) { - await provider.controller.executeNotebookCell(uri, handle, token); + await provider.controller.executeNotebookCell(uri, handle); } } - async executeNotebook2(viewType: string, uri: URI, kernelId: string, token: CancellationToken): Promise { - const kernel = this._notebookKernels.get(kernelId); - if (kernel) { - await kernel.executeNotebook(viewType, uri, undefined, token); + async cancelNotebook(viewType: string, uri: URI): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.cancelNotebookByAttachedKernel(viewType, uri); + } + + return; + } + + async cancelNotebookCell(viewType: string, uri: URI, handle: number): Promise { + const provider = this._notebookProviders.get(viewType); + if (provider) { + await provider.controller.cancelNotebookCell(uri, handle); } } - async executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string, token: CancellationToken): Promise { + async executeNotebook2(viewType: string, uri: URI, kernelId: string): Promise { const kernel = this._notebookKernels.get(kernelId); if (kernel) { - await kernel.executeNotebook(viewType, uri, handle, token); + await kernel.executeNotebook(viewType, uri, undefined); + } + } + + async executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string): Promise { + const kernel = this._notebookKernels.get(kernelId); + if (kernel) { + await kernel.executeNotebook(viewType, uri, handle); } } @@ -768,6 +844,17 @@ export class NotebookService extends Disposable implements INotebookService, ICu } updateActiveNotebookEditor(editor: INotebookEditor | null) { + this._activeEditorDisposables.clear(); + + if (editor) { + this._activeEditorDisposables.add(editor.onDidChangeKernel(() => { + this._onDidChangeNotebookActiveKernel.fire({ + uri: editor.uri!, + providerHandle: editor.activeKernel?.providerHandle, + kernelId: editor.activeKernel?.id + }); + })); + } this._onDidChangeActiveEditor.fire(editor ? editor.getId() : null); } @@ -847,7 +934,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu let provider = this._notebookProviders.get(modelData!.model.viewType); if (provider) { - provider.controller.removeNotebookDocument(modelData!.model); + provider.controller.removeNotebookDocument(modelData!.model.uri); + modelData!.model.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index a7005f54669..1e6fbfc2eb3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -45,14 +45,14 @@ export class CodeCell extends Disposable { const width = this.viewCell.layoutInfo.editorWidth; const lineNum = this.viewCell.lineCount; const lineHeight = this.viewCell.layoutInfo.fontInfo?.lineHeight || 17; - const totalHeight = this.viewCell.layoutInfo.editorHeight === 0 + const editorHeight = this.viewCell.layoutInfo.editorHeight === 0 ? lineNum * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING : this.viewCell.layoutInfo.editorHeight; this.layoutEditor( { width: width, - height: totalHeight + height: editorHeight } ); @@ -67,7 +67,7 @@ export class CodeCell extends Disposable { } const realContentHeight = templateData.editor?.getContentHeight(); - if (realContentHeight !== undefined && realContentHeight !== totalHeight) { + if (realContentHeight !== undefined && realContentHeight !== editorHeight) { this.onCellHeightChange(realContentHeight); } @@ -262,7 +262,7 @@ export class CodeCell extends Disposable { if (viewCell.outputs.length > 0) { let layoutCache = false; - if (this.viewCell.layoutInfo.totalHeight !== 0 && this.viewCell.layoutInfo.totalHeight > totalHeight) { + if (this.viewCell.layoutInfo.totalHeight !== 0 && this.viewCell.layoutInfo.editorHeight > editorHeight) { layoutCache = true; this.relayoutCell(); } @@ -277,7 +277,7 @@ export class CodeCell extends Disposable { this.renderOutput(currOutput, index, undefined); } - viewCell.editorHeight = totalHeight; + viewCell.editorHeight = editorHeight; if (layoutCache) { this.relayoutCellDebounced(); } else { @@ -285,7 +285,7 @@ export class CodeCell extends Disposable { } } else { // noop - viewCell.editorHeight = totalHeight; + viewCell.editorHeight = editorHeight; this.relayoutCell(); this.templateData.outputContainer!.style.display = 'none'; } @@ -328,7 +328,7 @@ export class CodeCell extends Disposable { ); } - renderOutput(currOutput: IProcessedOutput, index: number, beforeElement?: HTMLElement) { + private renderOutput(currOutput: IProcessedOutput, index: number, beforeElement?: HTMLElement) { if (!this.outputResizeListeners.has(currOutput)) { this.outputResizeListeners.set(currOutput, new DisposableStore()); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 5a61751216e..e8ebdb2be43 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -308,7 +308,9 @@ export abstract class BaseCellViewModel extends Disposable { } setSelections(selections: Selection[]) { - this._textEditor?.setSelections(selections); + if (selections.length) { + this._textEditor?.setSelections(selections); + } } getSelections() { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index cc709565f8b..312bfa882aa 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -9,7 +9,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, NotebookCellOutputsSplice, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BaseCellViewModel } from './baseCellViewModel'; @@ -45,7 +45,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } get editorHeight() { - return this._editorHeight; + throw new Error('editorHeight is write-only'); } private _hoveringOutput: boolean = false; @@ -87,7 +87,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod outputTotalHeight: 0, totalHeight: 0, indicatorHeight: 0, - bottomToolbarOffset: 0 + bottomToolbarOffset: 0, + layoutState: CodeCellLayoutState.Uninitialized }; } @@ -99,20 +100,41 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod // recompute this._ensureOutputsTop(); const outputTotalHeight = this._outputsTop!.getTotalValue(); - const totalHeight = EDITOR_TOOLBAR_HEIGHT + this.editorHeight + EDITOR_TOP_MARGIN + outputTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_STATUSBAR_HEIGHT + CELL_BOTTOM_MARGIN; - const indicatorHeight = this.editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; - const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + this.editorHeight + CELL_STATUSBAR_HEIGHT; + + let newState: CodeCellLayoutState; + let editorHeight: number; + let totalHeight: number; + if (!state.editorHeight && this._layoutInfo.layoutState === CodeCellLayoutState.FromCache) { + // No new editorHeight info - keep cached totalHeight and estimate editorHeight + editorHeight = this.estimateEditorHeight(state.font?.lineHeight); + totalHeight = this._layoutInfo.totalHeight; + newState = CodeCellLayoutState.FromCache; + } else if (state.editorHeight || this._layoutInfo.layoutState === CodeCellLayoutState.Measured) { + // Editor has been measured + editorHeight = this._editorHeight; + totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight); + newState = CodeCellLayoutState.Measured; + } else { + editorHeight = this.estimateEditorHeight(state.font?.lineHeight); + totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight); + newState = CodeCellLayoutState.Estimated; + } + + const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; + const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT; const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; + this._layoutInfo = { fontInfo: state.font || null, - editorHeight: this._editorHeight, + editorHeight, editorWidth, outputContainerOffset, outputTotalHeight, totalHeight, indicatorHeight, - bottomToolbarOffset: bottomToolbarOffset + bottomToolbarOffset, + layoutState: newState }; if (state.editorHeight || state.outputHeight) { @@ -128,7 +150,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod restoreEditorViewState(editorViewStates: editorCommon.ICodeEditorViewState | null, totalHeight?: number) { super.restoreEditorViewState(editorViewStates); - if (totalHeight !== undefined) { + if (totalHeight !== undefined && this._layoutInfo.layoutState !== CodeCellLayoutState.Measured) { this._layoutInfo = { fontInfo: this._layoutInfo.fontInfo, editorHeight: this._layoutInfo.editorHeight, @@ -137,36 +159,38 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod outputTotalHeight: this._layoutInfo.outputTotalHeight, totalHeight: totalHeight, indicatorHeight: this._layoutInfo.indicatorHeight, - bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset + bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset, + layoutState: CodeCellLayoutState.FromCache }; } } hasDynamicHeight() { - if (this.selfSizeMonitoring) { - // if there is an output rendered in the webview, it should always be false - return false; - } + // CodeCellVM always measures itself and controls its cell's height + return false; + } - if (this.outputs && this.outputs.length > 0) { - // if it contains output, it will be marked as dynamic height - // thus when it's being rendered, the list view will `probeHeight` - // inside which, we will check domNode's height directly instead of doing another `renderElement` with height undefined. - return true; - } - else { - return false; - } + firstLine(): string { + return this.getText().split('\n')[0]; } getHeight(lineHeight: number) { - if (this._layoutInfo.totalHeight === 0) { - return EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + this.lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING + BOTTOM_CELL_TOOLBAR_HEIGHT; + if (this._layoutInfo.layoutState === CodeCellLayoutState.Uninitialized) { + const editorHeight = this.estimateEditorHeight(lineHeight); + return this.computeTotalHeight(editorHeight, 0); } else { return this._layoutInfo.totalHeight; } } + private estimateEditorHeight(lineHeight: number | undefined = 20): number { + return this.lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + } + + private computeTotalHeight(editorHeight: number, outputsTotalHeight: number): number { + return EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT + outputsTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; + } + /** * Text model is used for editing. */ @@ -195,8 +219,9 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this._outputCollection[index] = height; this._ensureOutputsTop(); - this._outputsTop!.changeValue(index, height); - this.layoutChange({ outputHeight: true }); + if (this._outputsTop!.changeValue(index, height)) { + this.layoutChange({ outputHeight: true }); + } } getOutputOffsetInContainer(index: number) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 12cfb497361..a8c6187a0f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -154,16 +153,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD private _viewCells: CellViewModel[] = []; private _handleToViewCellMapping = new Map(); - private _currentTokenSource: CancellationTokenSource | undefined; - - get currentTokenSource(): CancellationTokenSource | undefined { - return this._currentTokenSource; - } - - set currentTokenSource(v: CancellationTokenSource | undefined) { - this._currentTokenSource = v; - } - get viewCells(): ICellViewModel[] { return this._viewCells; } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 925e0144e46..4bcb41cab07 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IProcessedOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import { InsertCellEdit, DeleteCellEdit, MoveCellEdit, SpliceCellsEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -116,8 +117,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel public viewType: string, public supportBackup: boolean, public uri: URI, - private _undoService: IUndoRedoService, - private _modelService: ITextModelService + @IUndoRedoService private _undoService: IUndoRedoService, + @ITextModelService private _modelService: ITextModelService ) { super(); this.cells = []; @@ -172,7 +173,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._increaseVersionId(); } - $applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], emitToExtHost: boolean, synchronous: boolean): boolean { + $applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], synchronous: boolean): boolean { if (modelVersionId !== this._versionId) { return false; } @@ -233,22 +234,20 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return [diff.start, diff.deleteCount, diff.toInsert] as [number, number, NotebookCellTextModel[]]; }); - if (emitToExtHost) { - this._onDidModelChangeProxy.fire({ - kind: NotebookCellsChangeType.ModelChange, - versionId: this._versionId, - changes: diffs.map(diff => [diff[0], diff[1], diff[2].map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - }))] as [number, number, IMainCellDto[]]) - }); - } + this._onDidModelChangeProxy.fire({ + kind: NotebookCellsChangeType.ModelChange, + versionId: this._versionId, + changes: diffs.map(diff => [diff[0], diff[1], diff[2].map(cell => ({ + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + }))] as [number, number, IMainCellDto[]]) + }); const undoDiff = diffs.map(diff => { const deletedCells = this.cells.slice(diff[0], diff[0] + diff[1]); @@ -266,6 +265,21 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return true; } + $handleEdit(label: string | undefined, undo: () => void, redo: () => void): void { + this._undoService.pushElement({ + type: UndoRedoElementType.Resource, + resource: this.uri, + label: label ?? nls.localize('defaultEditLabel', "Edit"), + undo: async () => { + undo(); + }, + redo: async () => { + redo(); + }, + }); + this.setDirty(true); + } + createSnapshot(preserveBOM?: boolean): ITextSnapshot { return new NotebookTextModelSnapshot(this); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index e7063754ffe..6f459d81629 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -53,6 +53,11 @@ export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER = [ export const BUILTIN_RENDERER_ID = '_builtin'; +export enum NotebookRunState { + Running = 1, + Idle = 2 +} + export const notebookDocumentMetadataDefaults: Required = { editable: true, runnable: true, @@ -60,7 +65,8 @@ export const notebookDocumentMetadataDefaults: Required; + providerHandle?: number; + executeNotebook(viewType: string, uri: URI, handle: number | undefined): Promise; + } export interface INotebookKernelInfoDto { @@ -641,6 +650,7 @@ export interface INotebookKernelInfoDto2 { label: string; extension: ExtensionIdentifier; extensionLocation: URI; + providerHandle?: number; description?: string; isPreferred?: boolean; preloads?: UriComponents[]; @@ -648,13 +658,16 @@ export interface INotebookKernelInfoDto2 { export interface INotebookKernelInfo2 extends INotebookKernelInfoDto2 { resolve(uri: URI, editorId: string, token: CancellationToken): Promise; - executeNotebookCell?(uri: URI, handle: number | undefined, token: CancellationToken): Promise; + executeNotebookCell?(uri: URI, handle: number | undefined): Promise; + cancelNotebookCell?(uri: URI, handle: number | undefined): Promise; } export interface INotebookKernelProvider { + providerExtensionId: string; + providerDescription?: string; selector: INotebookDocumentFilter; onDidChangeKernels: Event; provideKernels(uri: URI, token: CancellationToken): Promise; resolveKernel(editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; - executeNotebook(uri: URI, kernelId: string, handle: number | undefined, token: CancellationToken): Promise; + executeNotebook(uri: URI, kernelId: string, handle: number | undefined): Promise; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 35213ab164a..a0e064a2aec 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -14,7 +14,6 @@ import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapab import { basename } from 'vs/base/common/resources'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { IFileStatWithMetadata, IFileService } from 'vs/platform/files/common/files'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -142,42 +141,9 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN return this; // Make sure meanwhile someone else did not succeed in loading } - if (backup && backup.meta?.backupId === undefined) { - try { - return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF), options?.editorId); - } catch (error) { - // this.logService.error('[text file model] load() from backup', error); // ignore error and continue to load as file below - } - } - return this.loadFromProvider(false, options?.editorId, backup?.meta?.backupId); } - private async loadFromBackup(content: ITextBuffer, editorId?: string): Promise { - const fullRange = content.getRangeAt(0, content.getLength()); - const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF)); - - const notebook = await this._notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.languages, data.cells, editorId); - this._notebook = notebook!; - const newStats = await this._resolveStats(this.resource); - this._lastResolvedFileStat = newStats; - this._register(this._notebook); - - this._name = basename(this._notebook!.uri); - - this._register(this._notebook.onDidChangeContent(() => { - this._onDidChangeContent.fire(); - })); - this._register(this._notebook.onDidChangeDirty(() => { - this._onDidChangeDirty.fire(); - })); - - await this._backupFileService.discardBackup(this._workingCopyResource); - this._notebook.setDirty(true); - - return this; - } - private async loadFromProvider(forceReloadFromDisk: boolean, editorId: string | undefined, backupId: string | undefined) { const notebook = await this._notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, editorId, backupId); this._notebook = notebook!; diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index 7756d95221d..2ffcad9a909 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -18,7 +18,8 @@ export interface NotebookEditorDescriptor { readonly displayName: string; readonly selector: readonly NotebookSelector[]; readonly priority: NotebookEditorPriority; - readonly providerId?: string; + readonly providerExtensionId?: string; + readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; kernel?: INotebookKernelInfoDto; @@ -31,7 +32,8 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { readonly selector: readonly NotebookSelector[]; readonly priority: NotebookEditorPriority; // it's optional as the memento might not have it - readonly providerId?: string; + readonly providerExtensionId?: string; + readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; kernel?: INotebookKernelInfoDto; @@ -41,7 +43,8 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { this.displayName = descriptor.displayName; this.selector = descriptor.selector; this.priority = descriptor.priority; - this.providerId = descriptor.providerId; + this.providerExtensionId = descriptor.providerExtensionId; + this.providerDescription = descriptor.providerDescription; this.providerDisplayName = descriptor.providerDisplayName; this.providerExtensionLocation = descriptor.providerExtensionLocation; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 4ca02503000..74870976e2c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -9,7 +9,7 @@ import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/noteb import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; import { - INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, + INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, ICellEditOperation, NotebookCellOutputsSplice, IOrderedMimeType, IProcessedOutput, INotebookKernelProvider, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -22,12 +22,16 @@ export const INotebookService = createDecorator('notebookServi export interface IMainNotebookController { kernel: INotebookKernelInfoDto | undefined; - createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise; + supportBackup: boolean; + createNotebook(textModel: NotebookTextModel, editorId?: string, backupId?: string): Promise; + reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; - executeNotebookByAttachedKernel(viewType: string, uri: URI, token: CancellationToken): Promise; + executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise; + cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise; onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; - executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; - removeNotebookDocument(notebook: INotebookTextModel): Promise; + executeNotebookCell(uri: URI, handle: number): Promise; + cancelNotebookCell(uri: URI, handle: number): Promise; + removeNotebookDocument(uri: URI): Promise; save(uri: URI, token: CancellationToken): Promise; saveAs(uri: URI, target: URI, token: CancellationToken): Promise; backup(uri: URI, token: CancellationToken): Promise; @@ -43,6 +47,7 @@ export interface INotebookService { onNotebookDocumentRemove: Event; onNotebookDocumentAdd: Event; onDidChangeKernels: Event; + onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }>; registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; unregisterNotebookProvider(viewType: string): void; registerNotebookRenderer(id: string, renderer: INotebookRendererInfo): void; @@ -57,11 +62,13 @@ export interface INotebookService { getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise; getRendererInfo(id: string): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise; - createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise; - executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; - executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; - executeNotebook2(viewType: string, uri: URI, kernelId: string, token: CancellationToken): Promise; - executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string, token: CancellationToken): Promise; + getNotebookTextModel(uri: URI): NotebookTextModel | undefined; + executeNotebook(viewType: string, uri: URI): Promise; + cancelNotebook(viewType: string, uri: URI): Promise; + executeNotebookCell(viewType: string, uri: URI, handle: number): Promise; + cancelNotebookCell(viewType: string, uri: URI, handle: number): Promise; + executeNotebook2(viewType: string, uri: URI, kernelId: string): Promise; + executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string): Promise; getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; getNotebookProviderResourceRoots(): URI[]; diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index a5e5a19039c..23ffd1fb439 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -32,7 +32,7 @@ suite('NotebookTextModel', () => { textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [], textModelService)] }, { editType: CellEditType.Insert, index: 3, cells: [new TestCell(viewModel.viewType, 6, ['var f = 6;'], 'javascript', CellKind.Code, [], textModelService)] }, - ], true, true); + ], true); assert.equal(textModel.cells.length, 6); @@ -57,7 +57,7 @@ suite('NotebookTextModel', () => { textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [], textModelService)] }, { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 6, ['var f = 6;'], 'javascript', CellKind.Code, [], textModelService)] }, - ], true, true); + ], true); assert.equal(textModel.cells.length, 6); @@ -82,7 +82,7 @@ suite('NotebookTextModel', () => { textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Delete, index: 1, count: 1 }, { editType: CellEditType.Delete, index: 3, count: 1 }, - ], true, true); + ], true); assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); assert.equal(textModel.cells[1].getValue(), 'var c = 3;'); @@ -105,7 +105,7 @@ suite('NotebookTextModel', () => { textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Delete, index: 1, count: 1 }, { editType: CellEditType.Insert, index: 3, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [], textModelService)] }, - ], true, true); + ], true); assert.equal(textModel.cells.length, 4); @@ -130,7 +130,7 @@ suite('NotebookTextModel', () => { textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Delete, index: 1, count: 1 }, { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [], textModelService)] }, - ], true, true); + ], true); assert.equal(textModel.cells.length, 4); assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 7886563a6ad..70e0e6a2137 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -23,7 +23,6 @@ import { IProductService } from 'vs/platform/product/common/productService'; interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; - telemetry: { enableCrashReporter: boolean }; debug: { console: { wordWrap: boolean } }; editor: { accessibilitySupport: 'on' | 'off' | 'auto' }; } @@ -35,7 +34,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private nativeFullScreen: boolean | undefined; private clickThroughInactive: boolean | undefined; private updateMode: string | undefined; - private enableCrashReporter: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; @@ -92,12 +90,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo changed = true; } - // Crash reporter - if (typeof config.telemetry?.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { - this.enableCrashReporter = config.telemetry.enableCrashReporter; - changed = true; - } - // On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) { this.accessibilitySupport = config.editor.accessibilitySupport; diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index 2bf416bf119..3754d815819 100644 --- a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -22,6 +22,19 @@ import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/wo import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; import { IProductService } from 'vs/platform/product/common/productService'; +const MetaModulesToLookFor = [ + // Azure packages + '@azure', + '@azure/ai', + '@azure/core', + '@azure/cosmos', + '@azure/event', + '@azure/identity', + '@azure/keyvault', + '@azure/search', + '@azure/storage' +]; + const ModulesToLookFor = [ // Packages that suggest a node server 'express', @@ -69,27 +82,35 @@ const ModulesToLookFor = [ 'playwright-firefox', 'playwright-webkit' ]; + +const PyMetaModulesToLookFor = [ + 'azure-ai', + 'azure-cognitiveservices', + 'azure-core', + 'azure-cosmos', + 'azure-event', + 'azure-identity', + 'azure-keyvault', + 'azure-mgmt', + 'azure-ml', + 'azure-search', + 'azure-storage' +]; + 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-iothub-device-client', 'azure-loganalytics', 'azure-monitor', 'azure-servicebus', 'azure-servicefabric', - 'azure-storage', + 'azure-shell', 'azure-translator', - 'azure-iothub-device-client', 'adal', 'pydocumentdb', 'botbuilder-core', @@ -186,6 +207,15 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "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/ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/cosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/event" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/identity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/keyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/search" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/storage" : { "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 }, @@ -227,30 +257,31 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "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" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-core" : { "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-event" : { "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-identity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-iothub-device-client" : { "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-mgmt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-monitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-search" : { "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-shell" : { "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 }, @@ -370,16 +401,13 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { 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; + + for (const metaModule of PyMetaModulesToLookFor) { + if (packageName.startsWith(metaModule)) { + tags['workspace.py.' + metaModule] = true; + } } + if (!tags['workspace.py.any-azure']) { tags['workspace.py.any-azure'] = /azure/i.test(packageName); } @@ -419,24 +447,23 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { 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; - } + let dependencies = Object.keys(packageJsonContents['dependencies']).concat(Object.keys(packageJsonContents['devDependencies'])); + + for (let dependency of dependencies) { + if ('react-native' === dependency) { + tags['workspace.reactNative'] = true; + } else if ('tns-core-modules' === dependency) { + tags['workspace.nativescript'] = true; + } else if (ModulesToLookFor.indexOf(dependency) > -1) { + tags['workspace.npm.' + dependency] = true; } else { - if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { - tags['workspace.npm.' + module] = true; + for (const metaModule of MetaModulesToLookFor) { + if (dependency.startsWith(metaModule)) { + tags['workspace.npm.' + metaModule] = true; + } } } } - } catch (e) { // Ignore errors when resolving file or parsing file contents diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 1f84fabf713..7732517c737 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -334,7 +334,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.setExecutionContexts(); } - protected setExecutionContexts(custom: boolean = true, shell: boolean = false, process: boolean = false): void { + protected setExecutionContexts(custom: boolean = true, shell: boolean = true, process: boolean = true): void { const customContext = CustomExecutionSupportedContext.bindTo(this.contextKeyService); customContext.set(custom); const shellContext = ShellExecutionSupportedContext.bindTo(this.contextKeyService); @@ -530,6 +530,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } public registerTaskSystem(key: string, info: TaskSystemInfo): void { + if (info.platform === Platform.Platform.Web) { + this.setExecutionContexts(true, false, false); + } this._taskSystemInfos.set(key, info); } diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index 16470daef7b..4dbac3fbf91 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts @@ -25,10 +25,6 @@ interface WorkspaceFolderConfigurationResult { export class TaskService extends AbstractTaskService { private _configHasErrors: boolean = false; - protected setExecutionContexts(): void { - super.setExecutionContexts(true, true, true); - } - protected getTaskSystem(): ITaskSystem { if (this._taskSystem) { return this._taskSystem; diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 0a2a9f7bef9..5d590de2e93 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -55,8 +55,9 @@ export class OpenLatestReleaseNotesInBrowserAction extends Action { if (this.productService.releaseNotesUrl) { const uri = URI.parse(this.productService.releaseNotesUrl); await this.openerService.open(uri); + } else { + throw new Error(nls.localize('update.noReleaseNotesOnline', "This version of {0} does not have release notes online", this.productService.nameLong)); } - throw new Error('This version of Visual Studio Code does not have release notes online'); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts index 4d8c78b3534..5854248f34d 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/userDataSyncViews'; -import { ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, IViewDescriptorService } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -13,8 +13,8 @@ import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/act import { ContextKeyExpr, ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, dispose } from 'vs/base/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; import { IUserDataSyncWorkbenchService, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, MANUAL_SYNC_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { isEqual, basename } from 'vs/base/common/resources'; @@ -32,6 +32,12 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { Severity } from 'vs/platform/notification/common/notification'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; export class UserDataManualSyncViewPane extends TreeViewPane { @@ -41,9 +47,12 @@ export class UserDataManualSyncViewPane extends TreeViewPane { private syncButton!: Button; private cancelButton!: Button; + private readonly treeItems = new Map(); + constructor( options: IViewletViewOptions, @IEditorService private readonly editorService: IEditorService, + @IDialogService private readonly dialogService: IDialogService, @IProgressService private readonly progressService: IProgressService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @@ -71,7 +80,14 @@ export class UserDataManualSyncViewPane extends TreeViewPane { protected renderTreeView(container: HTMLElement): void { super.renderTreeView(DOM.append(container, DOM.$(''))); + this.createButtons(container); + const that = this; + this.treeView.message = localize('explanation', "Please go through each entry and accept the change to enable sync."); + this.treeView.dataProvider = { getChildren() { return that.getTreeItems(); } }; + } + + private createButtons(container: HTMLElement): void { this.buttonsContainer = DOM.append(container, DOM.$('.manual-sync-buttons-container')); this.syncButton = this._register(new Button(this.buttonsContainer)); @@ -84,22 +100,58 @@ export class UserDataManualSyncViewPane extends TreeViewPane { this.cancelButton.label = localize('cancel', "Cancel"); this._register(attachButtonStyler(this.cancelButton, this.themeService)); this._register(this.cancelButton.onDidClick(() => this.cancel())); - - this.treeView.dataProvider = new ManualSyncViewDataProvider(this.userDataSyncPreview); } protected layoutTreeView(height: number, width: number): void { - const buttonContainerHeight = 117; + const buttonContainerHeight = 78; this.buttonsContainer.style.height = `${buttonContainerHeight}px`; this.buttonsContainer.style.width = `${width}px`; + const numberOfChanges = this.userDataSyncPreview.resources.filter(r => r.syncResource !== SyncResource.GlobalState && (r.localChange !== Change.None || r.remoteChange !== Change.None)).length; - super.layoutTreeView(Math.min(height - buttonContainerHeight, 22 * numberOfChanges), width); + const messageHeight = 44; + super.layoutTreeView(Math.min(height - buttonContainerHeight, ((22 * numberOfChanges) + messageHeight)), width); } private updateSyncButtonEnablement(): void { this.syncButton.enabled = this.userDataSyncPreview.resources.every(c => c.syncResource === SyncResource.GlobalState || c.mergeState === MergeState.Accepted); } + private async getTreeItems(): Promise { + this.treeItems.clear(); + const roots: ITreeItem[] = []; + for (const resource of this.userDataSyncPreview.resources) { + if (resource.syncResource !== SyncResource.GlobalState && (resource.localChange !== Change.None || resource.remoteChange !== Change.None)) { + const handle = JSON.stringify(resource); + const treeItem = { + handle, + resourceUri: resource.remote, + label: { label: basename(resource.remote), strikethrough: resource.mergeState === MergeState.Accepted && (resource.localChange === Change.Deleted || resource.remoteChange === Change.Deleted) }, + description: getSyncAreaLabel(resource.syncResource), + collapsibleState: TreeItemCollapsibleState.None, + command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: handle }] }, + contextValue: `sync-resource-${resource.mergeState}` + }; + this.treeItems.set(handle, treeItem); + roots.push(treeItem); + } + } + return roots; + } + + private toUserDataSyncResourceGroup(handle: string): IUserDataSyncResource { + const parsed: IUserDataSyncResource = JSON.parse(handle); + return { + syncResource: parsed.syncResource, + local: URI.revive(parsed.local), + remote: URI.revive(parsed.remote), + merged: URI.revive(parsed.merged), + accepted: URI.revive(parsed.accepted), + localChange: parsed.localChange, + remoteChange: parsed.remoteChange, + mergeState: parsed.mergeState, + }; + } + private registerActions(): void { const that = this; @@ -119,7 +171,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.acceptRemote(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.acceptRemote(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } })); @@ -139,7 +191,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.acceptLocal(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.acceptLocal(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } })); @@ -149,7 +201,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { super({ id: `workbench.actions.sync.merge`, title: localize('workbench.actions.sync.merge', "Merge"), - icon: Codicon.gitMerge, + icon: Codicon.merge, menu: { id: MenuId.ViewItemContext, when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), @@ -159,7 +211,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.mergeResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.mergeResource(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } })); @@ -179,7 +231,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.discardResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.discardResource(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } })); @@ -191,7 +243,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - const previewResource: IUserDataSyncResource = ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle); + const previewResource: IUserDataSyncResource = that.toUserDataSyncResourceGroup(handle.$treeItemHandle); return that.open(previewResource); } })); @@ -200,7 +252,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { private async acceptLocal(userDataSyncResource: IUserDataSyncResource): Promise { await this.withProgress(async () => { const content = await this.userDataSyncService.resolveContent(userDataSyncResource.local); - await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local, content || ''); + await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local, content); }); await this.reopen(userDataSyncResource); } @@ -208,14 +260,20 @@ export class UserDataManualSyncViewPane extends TreeViewPane { private async acceptRemote(userDataSyncResource: IUserDataSyncResource): Promise { await this.withProgress(async () => { const content = await this.userDataSyncService.resolveContent(userDataSyncResource.remote); - await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote, content || ''); + await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote, content); }); await this.reopen(userDataSyncResource); } private async mergeResource(previewResource: IUserDataSyncResource): Promise { await this.withProgress(() => this.userDataSyncPreview.merge(previewResource.merged)); + previewResource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local))!; await this.reopen(previewResource); + if (previewResource.mergeState === MergeState.Conflict) { + await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected."), [], { + detail: localize('resolve', "Unable to merge due to conflicts. Please resolve them to continue.") + }); + } } private async discardResource(previewResource: IUserDataSyncResource): Promise { @@ -224,19 +282,16 @@ export class UserDataManualSyncViewPane extends TreeViewPane { } private async apply(): Promise { + this.closeAll(); this.syncButton.label = localize('turning on', "Turning on..."); this.syncButton.enabled = false; this.cancelButton.enabled = false; - return this.withProgress(async () => { - for (const resource of this.userDataSyncPreview.resources) { - if (resource.syncResource === SyncResource.GlobalState) { - await this.userDataSyncPreview.merge(resource.merged); - } else { - this.close(resource); - } - } - await this.userDataSyncPreview.apply(); - }); + try { + await this.withProgress(async () => this.userDataSyncPreview.apply()); + } catch (error) { + this.syncButton.enabled = false; + this.cancelButton.enabled = true; + } } private async cancel(): Promise { @@ -256,7 +311,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { const leftResource = previewResource.remote; const rightResource = previewResource.mergeState === MergeState.Conflict ? previewResource.merged : previewResource.local; const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(leftResource)); - const rightResourceName = previewResource.mergeState === MergeState.Conflict ? localize('merge preview', "{0} (Merge Preview)", basename(rightResource)) + const rightResourceName = previewResource.mergeState === MergeState.Conflict ? localize('merges', "{0} (Merges)", basename(rightResource)) : localize({ key: 'rightResourceName', comment: ['local as in file in disk'] }, "{0} (Local)", basename(rightResource)); await this.editorService.openEditor({ leftResource, @@ -274,11 +329,15 @@ export class UserDataManualSyncViewPane extends TreeViewPane { this.close(previewResource); const resource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local)); if (resource) { + // select the resource + await this.treeView.refresh(); + this.treeView.setSelection([this.treeItems.get(JSON.stringify(resource))!]); + await this.open(resource); } } - private close(previewResource: IUserDataSyncResource) { + private close(previewResource: IUserDataSyncResource): void { for (const input of this.editorService.editors) { if (input instanceof DiffEditorInput) { // Close all diff editors @@ -307,54 +366,18 @@ export class UserDataManualSyncViewPane extends TreeViewPane { } } + private closeAll() { + for (const previewResource of this.userDataSyncPreview.resources) { + this.close(previewResource); + } + } + private withProgress(task: () => Promise): Promise { return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task); } } -class ManualSyncViewDataProvider implements ITreeViewDataProvider { - - constructor( - private readonly userDataSyncPreview: IUserDataSyncPreview - ) { - } - - async getChildren(): Promise { - const roots: ITreeItem[] = []; - for (const resource of this.userDataSyncPreview.resources) { - if (resource.syncResource !== SyncResource.GlobalState && (resource.localChange !== Change.None || resource.remoteChange !== Change.None)) { - const handle = JSON.stringify(resource); - roots.push({ - handle, - resourceUri: resource.remote, - label: { label: basename(resource.remote), strikethrough: resource.mergeState === MergeState.Accepted && (resource.localChange === Change.Deleted || resource.remoteChange === Change.Deleted) }, - description: getSyncAreaLabel(resource.syncResource), - collapsibleState: TreeItemCollapsibleState.None, - command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: handle }] }, - contextValue: `sync-resource-${resource.mergeState}` - }); - } - } - return roots; - } - - static toUserDataSyncResourceGroup(handle: string): IUserDataSyncResource { - const parsed: IUserDataSyncResource = JSON.parse(handle); - return { - syncResource: parsed.syncResource, - local: URI.revive(parsed.local), - remote: URI.revive(parsed.remote), - merged: URI.revive(parsed.merged), - accepted: URI.revive(parsed.accepted), - localChange: parsed.localChange, - remoteChange: parsed.remoteChange, - mergeState: parsed.mergeState, - }; - } - -} - class UserDataSyncResourcesDecorationProvider extends Disposable implements IDecorationsProvider { readonly label: string = localize('label', "UserDataSyncResources"); @@ -380,3 +403,102 @@ class UserDataSyncResourcesDecorationProvider extends Disposable implements IDec return undefined; } } + +type AcceptChangesClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + +class AcceptChangesContribution extends Disposable implements IEditorContribution { + + static get(editor: ICodeEditor): AcceptChangesContribution { + return editor.getContribution(AcceptChangesContribution.ID); + } + + public static readonly ID = 'editor.contrib.acceptChangesButton'; + + private acceptChangesButton: FloatingClickWidget | undefined; + + constructor( + private editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, + ) { + super(); + + this.update(); + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.editor.onDidChangeModel(() => this.update())); + this._register(this.userDataSyncService.onDidChangeConflicts(() => this.update())); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update())); + } + + private update(): void { + if (!this.shouldShowButton(this.editor)) { + this.disposeAcceptChangesWidgetRenderer(); + return; + } + + this.createAcceptChangesWidgetRenderer(); + } + + private shouldShowButton(editor: ICodeEditor): boolean { + const model = editor.getModel(); + if (!model) { + return false; // we need a model + } + + const userDataSyncResource = this.getUserDataSyncResource(model.uri); + if (!userDataSyncResource) { + return false; + } + + return true; + } + + private createAcceptChangesWidgetRenderer(): void { + if (!this.acceptChangesButton) { + const resource = this.editor.getModel()!.uri; + const userDataSyncResource = this.getUserDataSyncResource(resource)!; + + const isRemoteResource = isEqual(userDataSyncResource.remote, resource); + const isLocalResource = isEqual(userDataSyncResource.local, resource); + const label = isRemoteResource ? localize('accept remote', "Accept Remote") + : isLocalResource ? localize('accept local', "Accept Local") + : localize('accept merges', "Accept Merges"); + + this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, label, null); + this._register(this.acceptChangesButton.onClick(async () => { + const model = this.editor.getModel(); + if (model) { + this.telemetryService.publicLog2<{ source: string, action: string }, AcceptChangesClassification>('sync/acceptChanges', { source: userDataSyncResource.syncResource, action: isRemoteResource ? 'acceptRemote' : isLocalResource ? 'acceptLocal' : 'acceptMerges' }); + await this.userDataSyncWorkbenchService.userDataSyncPreview.accept(userDataSyncResource.syncResource, model.uri, model.getValue()); + } + })); + + this.acceptChangesButton.render(); + } + } + + private getUserDataSyncResource(resource: URI): IUserDataSyncResource | undefined { + return this.userDataSyncWorkbenchService.userDataSyncPreview.resources.find(r => isEqual(resource, r.local) || isEqual(resource, r.remote) || isEqual(resource, r.merged)); + } + + private disposeAcceptChangesWidgetRenderer(): void { + dispose(this.acceptChangesButton); + this.acceptChangesButton = undefined; + } + + dispose(): void { + this.disposeAcceptChangesWidgetRenderer(); + super.dispose(); + } +} + +registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 35739d96ca5..0ba2747b20b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -140,6 +140,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateGlobalActivityBadge(); })); this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts))); + this._register(userDataAutoSyncService.onDidChangeEnablement(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts))); this._register(userDataSyncService.onSyncErrors(errors => this.onSynchronizerErrors(errors))); this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error))); @@ -197,14 +198,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } }, { - label: localize('accept local', "Accept Local"), + label: localize('accept merges', "Accept Merges"), run: () => { this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: 'acceptLocal' }); this.acceptLocal(syncResource, conflicts); } }, { - label: localize('show conflicts', "Show Conflicts"), + label: localize('show merges', "Show Merges"), run: () => { this.telemetryService.publicLog2<{ source: string, action?: string }, SyncConflictsClassification>('sync/showConflicts', { source: syncResource }); this.handleConflicts([syncResource, conflicts]); @@ -308,10 +309,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo case UserDataSyncErrorCode.IncompatibleRemoteContent: this.notificationService.notify({ severity: Severity.Error, - message: localize('error reset required', "Preferences sync is disabled because your data in the cloud is older than that of in the client. Please reset your data in the cloud before turning on sync."), + message: localize('error reset required', "Preferences sync is disabled because your data in the cloud is older than that of the client. Please clear your data in the cloud before turning on sync."), actions: { primary: [ - new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), + new Action('reset', localize('reset', "Clear Data in Cloud..."), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.showSyncActivity()) ] } @@ -389,7 +390,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo let clazz: string | undefined; let priority: number | undefined = undefined; - if (this.userDataSyncService.conflicts.length) { + if (this.userDataSyncService.conflicts.length && this.userDataAutoSyncService.isEnabled()) { badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, [, conflicts]) => { return result + conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected")); } else if (this.turningOnSync) { badge = new ProgressBadge(() => localize('turning on syncing', "Turning on Preferences Sync...")); @@ -464,10 +465,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo case UserDataSyncErrorCode.IncompatibleRemoteContent: this.notificationService.notify({ severity: Severity.Error, - message: localize('error reset required while starting sync', "Preferences sync cannot be turned on because your data in the cloud is older than that of in the client. Please reset your data in the cloud before turning on sync."), + message: localize('error reset required while starting sync', "Preferences sync cannot be turned on because your data in the cloud is older than that of the client. Please clear your data in the cloud before turning on sync."), actions: { primary: [ - new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), + new Action('reset', localize('reset', "Clear Data in Cloud..."), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.showSyncActivity()) ] } @@ -650,18 +651,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async handleConflicts([syncResource, conflicts]: [SyncResource, IResourcePreview[]]): Promise { for (const conflict of conflicts) { - let label: string | undefined = undefined; - if (syncResource === SyncResource.Settings) { - label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); - } else if (syncResource === SyncResource.Keybindings) { - label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); - } else if (syncResource === SyncResource.Snippets) { - label = localize('snippets conflicts preview', "User Snippet Conflicts (Remote ↔ Local) - {0}", basename(conflict.previewResource)); - } + const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); + const rightResourceName = localize('merges', "{0} (Merges)", basename(conflict.previewResource)); await this.editorService.openEditor({ leftResource: conflict.remoteResource, rightResource: conflict.previewResource, - label, + label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), options: { preserveFocus: false, pinned: true, @@ -1130,7 +1125,6 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, - @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, ) { super(); @@ -1159,6 +1153,10 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio return false; // we need a model } + if (!this.userDataAutoSyncService.isEnabled()) { + return false; + } + const syncResourceConflicts = this.getSyncResourceConflicts(model.uri); if (!syncResourceConflicts) { return false; @@ -1181,40 +1179,36 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio const [syncResource, conflicts] = this.getSyncResourceConflicts(resource)!; const isRemote = conflicts.some(({ remoteResource }) => isEqual(remoteResource, resource)); const acceptRemoteLabel = localize('accept remote', "Accept Remote"); - const acceptLocalLabel = localize('accept merge preview', "Accept Merge Preview"); - this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptLocalLabel, null); + const acceptMergesLabel = localize('accept merges', "Accept Merges"); + this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptMergesLabel, null); this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); const syncAreaLabel = getSyncAreaLabel(syncResource); - if (this.userDataAutoSyncService.isEnabled()) { - const result = await this.dialogService.confirm({ - type: 'info', - title: isRemote - ? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel) - : localize('Sync accept local', "Preferences Sync: {0}", acceptLocalLabel), - message: isRemote - ? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) - : localize('confirm replace and overwrite remote', "Would you like to accept local {0} and replace remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), - primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel - }); - if (result.confirmed) { - try { - await this.userDataSyncService.accept(syncResource, model.uri, model.getValue(), true); - } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) { - const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(syncResourceCoflicts => syncResourceCoflicts[0] === syncResource)[0]; - if (syncResourceCoflicts && conflicts.some(conflict => isEqual(conflict.previewResource, model.uri) || isEqual(conflict.remoteResource, model.uri))) { - this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); - } - } else { - this.notificationService.error(e); + const result = await this.dialogService.confirm({ + type: 'info', + title: isRemote + ? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel) + : localize('Sync accept merges', "Preferences Sync: {0}", acceptMergesLabel), + message: isRemote + ? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) + : localize('confirm replace and overwrite remote', "Would you like to accept merges and replace remote {0}?", syncAreaLabel.toLowerCase()), + primaryButton: isRemote ? acceptRemoteLabel : acceptMergesLabel + }); + if (result.confirmed) { + try { + await this.userDataSyncService.accept(syncResource, model.uri, model.getValue(), true); + } catch (e) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) { + const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(syncResourceCoflicts => syncResourceCoflicts[0] === syncResource)[0]; + if (syncResourceCoflicts && conflicts.some(conflict => isEqual(conflict.previewResource, model.uri) || isEqual(conflict.remoteResource, model.uri))) { + this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); } + } else { + this.notificationService.error(e); } } - } else { - await this.userDataSyncWorkbenchService.userDataSyncPreview.accept(syncResource, model.uri, model.getValue()); } } })); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index c4a1c631cbb..8fcd161c6eb 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -55,7 +55,7 @@ export class UserDataSyncViewPaneContainer extends ViewPaneContainer { @IExtensionService extensionService: IExtensionService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, ) { - super(containerId, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(containerId, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); } getActions(): IAction[] { @@ -67,7 +67,7 @@ export class UserDataSyncViewPaneContainer extends ViewPaneContainer { getSecondaryActions(): IAction[] { return [ - new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), + new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Clear Data in Cloud..."), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), ]; } diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts index 50398d48086..f21878522bb 100644 --- a/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts @@ -17,6 +17,8 @@ import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common import { CancellationToken } from 'vs/base/common/cancellation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution { @@ -33,6 +35,8 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IProductService private readonly productService: IProductService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService ) { } protected async handleTelemetryOptOut(): Promise { @@ -136,10 +140,10 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution }, { label: noLabel, - run: () => { + run: async () => { logTelemetry(true); this.configurationService.updateValue('telemetry.enableTelemetry', false); - this.configurationService.updateValue('telemetry.enableCrashReporter', false); + await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['enable-crash-reporter'], value: false }], true); } } ], @@ -164,9 +168,11 @@ export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut { @IExperimentService experimentService: IExperimentService, @IConfigurationService configurationService: IConfigurationService, @IExtensionGalleryService galleryService: IExtensionGalleryService, - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, + @IJSONEditingService jsonEditingService: IJSONEditingService ) { - super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService); + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); this.handleTelemetryOptOut(); } diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts index 0adc911b514..205e0e00d6a 100644 --- a/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts @@ -13,6 +13,8 @@ import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common import { IProductService } from 'vs/platform/product/common/productService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { AbstractTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { @@ -27,9 +29,11 @@ export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { @IConfigurationService configurationService: IConfigurationService, @IExtensionGalleryService galleryService: IExtensionGalleryService, @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, + @IJSONEditingService jsonEditingService: IJSONEditingService, @IElectronService private readonly electronService: IElectronService ) { - super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService); + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); this.handleTelemetryOptOut(); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 6b06f122a5e..1257a13daf7 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -6,14 +6,14 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; -import { equals, deepClone } from 'vs/base/common/objects'; +import { equals } from 'vs/base/common/objects'; import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; -import { ITelemetryService, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; import { IRunActionInWindowRequest, IRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/node/window'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; @@ -23,8 +23,7 @@ import { setFullscreen, getZoomLevel } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes'; -import { crashReporter, ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -34,8 +33,8 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; -import { IProductService, IAppCenterConfiguration } from 'vs/platform/product/common/productService'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { IProductService } from 'vs/platform/product/common/productService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -47,7 +46,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { MenubarControl } from '../browser/parts/titlebar/menubarControl'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { IPreferencesService } from '../services/preferences/common/preferences'; import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { IMenubarService } from 'vs/platform/menubar/electron-sandbox/menubar'; @@ -109,9 +108,8 @@ export class NativeWindow extends Disposable { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, - @IStorageService private readonly storageService: IStorageService, @IProductService private readonly productService: IProductService, - @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService ) { super(); @@ -417,23 +415,6 @@ export class NativeWindow extends Disposable { // Touchbar menu (if enabled) this.updateTouchbarMenu(); - - // Crash reporter (if enabled) - if (!this.environmentService.disableCrashReporter && this.configurationService.getValue('telemetry.enableCrashReporter')) { - const companyName = this.productService.crashReporter?.companyName || 'Microsoft'; - const productName = this.productService.crashReporter?.productName || this.productService.nameShort; - - // With a provided crash reporter directory, crashes - // will be stored only locally in that folder - if (this.environmentService.crashReporterDirectory) { - this.setupCrashReporter(companyName, productName, undefined, this.environmentService.crashReporterDirectory); - } - - // With appCenter enabled, crashes will be uploaded - else if (this.productService.appCenter) { - this.setupCrashReporter(companyName, productName, this.productService.appCenter, undefined); - } - } } private setupOpenHandlers(): void { @@ -551,42 +532,6 @@ export class NativeWindow extends Disposable { } } - private async setupCrashReporter(companyName: string, productName: string, appCenter: IAppCenterConfiguration, crashesDirectory: undefined): Promise; - private async setupCrashReporter(companyName: string, productName: string, appCenter: undefined, crashesDirectory: string): Promise; - private async setupCrashReporter(companyName: string, productName: string, appCenter: IAppCenterConfiguration | undefined, crashesDirectory: string | undefined): Promise { - let submitURL: string | undefined = undefined; - if (appCenter) { - submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin; - } - - const info = await this.telemetryService.getTelemetryInfo(); - const crashReporterId = this.storageService.get(crashReporterIdStorageKey, StorageScope.GLOBAL)!; - - // base options with product info - const options: CrashReporterStartOptions = { - companyName, - productName, - submitURL: (submitURL?.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', info.sessionId)) || '', - extra: { - vscode_version: this.productService.version, - vscode_commit: this.productService.commit || '' - }, - - // If `crashesDirectory` is specified, we do not upload - uploadToServer: !crashesDirectory, - }; - - // start crash reporter in the main process first. - // On windows crashpad excepts a name pipe for the client to connect, - // this pipe is created by crash reporter initialization from the main process, - // changing this order of initialization will cause issues. - // For more info: https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/doc/overview_design.md#normal-registration - await this.electronService.startCrashReporter(options); - - // start crash reporter right here - crashReporter.start(deepClone(options)); - } - private onAddFoldersRequest(request: IAddFoldersRequest): void { // Buffer all pending requests diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 0296383f560..f608c5ff52d 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -344,6 +344,14 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; type: 'string', markdownDescription: nls.localize('argv.forceColorProfile', 'Allows to override the color profile to use. If you experience colors appear badly, try to set this to `srgb` and restart.') }, + 'enable-crash-reporter': { + type: 'boolean', + markdownDescription: nls.localize('argv.enableCrashReporter', 'Allows to disable crash reporting, should restart the app if the value is changed.') + }, + 'crash-reporter-id': { + type: 'string', + markdownDescription: nls.localize('argv.crashReporterId', 'Unique id used for correlating crash reports sent from this app instance.') + }, 'enable-proposed-api': { type: 'array', description: nls.localize('argv.enebleProposedApi', "Enable proposed APIs for a list of extension ids (such as \`vscode.git\`). Proposed APIs are unstable and subject to breaking without warning at any time. This should only be set for extension development and testing purposes."), diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 20da095a459..46f084aff4c 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -17,8 +17,8 @@ export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmen readonly configuration: INativeEnvironmentConfiguration; - readonly disableCrashReporter: boolean; readonly crashReporterDirectory?: string; + readonly crashReporterId?: string; readonly cliPath: string; diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index c1c1070636e..d78328501c9 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -49,7 +49,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService declare readonly _serviceBrand: undefined; private readonly systemExtensionsPromise: Promise = Promise.resolve([]); - private readonly staticExtensionsPromise: Promise = Promise.resolve([]); + private readonly defaultExtensionsPromise: Promise = Promise.resolve([]); private readonly extensionsResource: URI | undefined = undefined; private readonly userExtensionsResourceLimiter: Queue = new Queue(); @@ -64,11 +64,11 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService if (isWeb) { this.extensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json'); this.systemExtensionsPromise = this.builtinExtensionsScannerService.scanBuiltinExtensions(); - this.staticExtensionsPromise = this.readStaticExtensions(); + this.defaultExtensionsPromise = this.readDefaultExtensions(); } } - private async readStaticExtensions(): Promise { + private async readDefaultExtensions(): Promise { const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : []; const defaultUserWebExtensions = await this.readDefaultUserWebExtensions(); return [...staticExtensions, ...defaultUserWebExtensions].map(e => ({ @@ -111,7 +111,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService extensions.push(...systemExtensions); } if (type === undefined || type === ExtensionType.User) { - const staticExtensions = await this.staticExtensionsPromise; + const staticExtensions = await this.defaultExtensionsPromise; extensions.push(...staticExtensions); const userExtensions = await this.scanUserExtensions(); extensions.push(...userExtensions); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index acf4b587af0..5e30ddba98e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -582,8 +582,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!; - localProcessExtensionHost.start(localProcessExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); + const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess); + if (localProcessExtensionHost) { + localProcessExtensionHost.start(localProcessExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); + } const localWebWorkerExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalWebWorker); if (localWebWorkerExtensionHost) { diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index fdf086f74c7..7ec855ce59b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -44,6 +44,7 @@ import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { isUUID } from 'vs/base/common/uuid'; export interface ILocalProcessExtensionHostInitData { readonly autoStart: boolean; @@ -182,18 +183,23 @@ export class LocalProcessExtensionHost implements IExtensionHost { opts.execArgv = ['--inspect-port=0']; } - // Enable the crash reporter depending on environment for local reporting - const crashesDirectory = this._environmentService.crashReporterDirectory; - if (crashesDirectory) { - const crashReporterOptions: CrashReporterStartOptions = { + // On linux crash reporter needs to be started on child node processes explicitly + if (platform.isLinux) { + const crashReporterStartOptions: CrashReporterStartOptions = { companyName: this._productService.crashReporter?.companyName || 'Microsoft', productName: this._productService.crashReporter?.productName || this._productService.nameShort, submitURL: '', - uploadToServer: false, - crashesDirectory + uploadToServer: false }; - - opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions); + const crashReporterId = this._environmentService.crashReporterId; // crashReporterId is set by the main process only when crash reporting is enabled by the user. + const appcenter = this._productService.appCenter; + const uploadCrashesToServer = !this._environmentService.crashReporterDirectory; // only upload unless --crash-reporter-directory is provided + if (uploadCrashesToServer && appcenter && crashReporterId && isUUID(crashReporterId)) { + const submitURL = appcenter[`linux-x64`]; + crashReporterStartOptions.submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); + crashReporterStartOptions.uploadToServer = true; + } + opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions); } // Run Extension Host as fork of current process diff --git a/src/vs/workbench/services/textfile/common/encoding.ts b/src/vs/workbench/services/textfile/common/encoding.ts index 7c390e47caa..c4f80a86739 100644 --- a/src/vs/workbench/services/textfile/common/encoding.ts +++ b/src/vs/workbench/services/textfile/common/encoding.ts @@ -446,3 +446,244 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, aut return { seemsBinary, encoding }; } + +export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = { + utf8: { + labelLong: 'UTF-8', + labelShort: 'UTF-8', + order: 1, + alias: 'utf8bom' + }, + utf8bom: { + labelLong: 'UTF-8 with BOM', + labelShort: 'UTF-8 with BOM', + encodeOnly: true, + order: 2, + alias: 'utf8' + }, + utf16le: { + labelLong: 'UTF-16 LE', + labelShort: 'UTF-16 LE', + order: 3 + }, + utf16be: { + labelLong: 'UTF-16 BE', + labelShort: 'UTF-16 BE', + order: 4 + }, + windows1252: { + labelLong: 'Western (Windows 1252)', + labelShort: 'Windows 1252', + order: 5 + }, + iso88591: { + labelLong: 'Western (ISO 8859-1)', + labelShort: 'ISO 8859-1', + order: 6 + }, + iso88593: { + labelLong: 'Western (ISO 8859-3)', + labelShort: 'ISO 8859-3', + order: 7 + }, + iso885915: { + labelLong: 'Western (ISO 8859-15)', + labelShort: 'ISO 8859-15', + order: 8 + }, + macroman: { + labelLong: 'Western (Mac Roman)', + labelShort: 'Mac Roman', + order: 9 + }, + cp437: { + labelLong: 'DOS (CP 437)', + labelShort: 'CP437', + order: 10 + }, + windows1256: { + labelLong: 'Arabic (Windows 1256)', + labelShort: 'Windows 1256', + order: 11 + }, + iso88596: { + labelLong: 'Arabic (ISO 8859-6)', + labelShort: 'ISO 8859-6', + order: 12 + }, + windows1257: { + labelLong: 'Baltic (Windows 1257)', + labelShort: 'Windows 1257', + order: 13 + }, + iso88594: { + labelLong: 'Baltic (ISO 8859-4)', + labelShort: 'ISO 8859-4', + order: 14 + }, + iso885914: { + labelLong: 'Celtic (ISO 8859-14)', + labelShort: 'ISO 8859-14', + order: 15 + }, + windows1250: { + labelLong: 'Central European (Windows 1250)', + labelShort: 'Windows 1250', + order: 16 + }, + iso88592: { + labelLong: 'Central European (ISO 8859-2)', + labelShort: 'ISO 8859-2', + order: 17 + }, + cp852: { + labelLong: 'Central European (CP 852)', + labelShort: 'CP 852', + order: 18 + }, + windows1251: { + labelLong: 'Cyrillic (Windows 1251)', + labelShort: 'Windows 1251', + order: 19 + }, + cp866: { + labelLong: 'Cyrillic (CP 866)', + labelShort: 'CP 866', + order: 20 + }, + iso88595: { + labelLong: 'Cyrillic (ISO 8859-5)', + labelShort: 'ISO 8859-5', + order: 21 + }, + koi8r: { + labelLong: 'Cyrillic (KOI8-R)', + labelShort: 'KOI8-R', + order: 22 + }, + koi8u: { + labelLong: 'Cyrillic (KOI8-U)', + labelShort: 'KOI8-U', + order: 23 + }, + iso885913: { + labelLong: 'Estonian (ISO 8859-13)', + labelShort: 'ISO 8859-13', + order: 24 + }, + windows1253: { + labelLong: 'Greek (Windows 1253)', + labelShort: 'Windows 1253', + order: 25 + }, + iso88597: { + labelLong: 'Greek (ISO 8859-7)', + labelShort: 'ISO 8859-7', + order: 26 + }, + windows1255: { + labelLong: 'Hebrew (Windows 1255)', + labelShort: 'Windows 1255', + order: 27 + }, + iso88598: { + labelLong: 'Hebrew (ISO 8859-8)', + labelShort: 'ISO 8859-8', + order: 28 + }, + iso885910: { + labelLong: 'Nordic (ISO 8859-10)', + labelShort: 'ISO 8859-10', + order: 29 + }, + iso885916: { + labelLong: 'Romanian (ISO 8859-16)', + labelShort: 'ISO 8859-16', + order: 30 + }, + windows1254: { + labelLong: 'Turkish (Windows 1254)', + labelShort: 'Windows 1254', + order: 31 + }, + iso88599: { + labelLong: 'Turkish (ISO 8859-9)', + labelShort: 'ISO 8859-9', + order: 32 + }, + windows1258: { + labelLong: 'Vietnamese (Windows 1258)', + labelShort: 'Windows 1258', + order: 33 + }, + gbk: { + labelLong: 'Simplified Chinese (GBK)', + labelShort: 'GBK', + order: 34 + }, + gb18030: { + labelLong: 'Simplified Chinese (GB18030)', + labelShort: 'GB18030', + order: 35 + }, + cp950: { + labelLong: 'Traditional Chinese (Big5)', + labelShort: 'Big5', + order: 36 + }, + big5hkscs: { + labelLong: 'Traditional Chinese (Big5-HKSCS)', + labelShort: 'Big5-HKSCS', + order: 37 + }, + shiftjis: { + labelLong: 'Japanese (Shift JIS)', + labelShort: 'Shift JIS', + order: 38 + }, + eucjp: { + labelLong: 'Japanese (EUC-JP)', + labelShort: 'EUC-JP', + order: 39 + }, + euckr: { + labelLong: 'Korean (EUC-KR)', + labelShort: 'EUC-KR', + order: 40 + }, + windows874: { + labelLong: 'Thai (Windows 874)', + labelShort: 'Windows 874', + order: 41 + }, + iso885911: { + labelLong: 'Latin/Thai (ISO 8859-11)', + labelShort: 'ISO 8859-11', + order: 42 + }, + koi8ru: { + labelLong: 'Cyrillic (KOI8-RU)', + labelShort: 'KOI8-RU', + order: 43 + }, + koi8t: { + labelLong: 'Tajik (KOI8-T)', + labelShort: 'KOI8-T', + order: 44 + }, + gb2312: { + labelLong: 'Simplified Chinese (GB 2312)', + labelShort: 'GB 2312', + order: 45 + }, + cp865: { + labelLong: 'Nordic DOS (CP 865)', + labelShort: 'CP 865', + order: 46 + }, + cp850: { + labelLong: 'Western European DOS (CP 850)', + labelShort: 'CP 850', + order: 47 + } +}; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 6310f919b72..d974bea6d5e 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -506,244 +506,3 @@ export function toBufferOrReadable(value: string | ITextSnapshot | undefined): V return new TextSnapshotReadable(value); } - -export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = { - utf8: { - labelLong: 'UTF-8', - labelShort: 'UTF-8', - order: 1, - alias: 'utf8bom' - }, - utf8bom: { - labelLong: 'UTF-8 with BOM', - labelShort: 'UTF-8 with BOM', - encodeOnly: true, - order: 2, - alias: 'utf8' - }, - utf16le: { - labelLong: 'UTF-16 LE', - labelShort: 'UTF-16 LE', - order: 3 - }, - utf16be: { - labelLong: 'UTF-16 BE', - labelShort: 'UTF-16 BE', - order: 4 - }, - windows1252: { - labelLong: 'Western (Windows 1252)', - labelShort: 'Windows 1252', - order: 5 - }, - iso88591: { - labelLong: 'Western (ISO 8859-1)', - labelShort: 'ISO 8859-1', - order: 6 - }, - iso88593: { - labelLong: 'Western (ISO 8859-3)', - labelShort: 'ISO 8859-3', - order: 7 - }, - iso885915: { - labelLong: 'Western (ISO 8859-15)', - labelShort: 'ISO 8859-15', - order: 8 - }, - macroman: { - labelLong: 'Western (Mac Roman)', - labelShort: 'Mac Roman', - order: 9 - }, - cp437: { - labelLong: 'DOS (CP 437)', - labelShort: 'CP437', - order: 10 - }, - windows1256: { - labelLong: 'Arabic (Windows 1256)', - labelShort: 'Windows 1256', - order: 11 - }, - iso88596: { - labelLong: 'Arabic (ISO 8859-6)', - labelShort: 'ISO 8859-6', - order: 12 - }, - windows1257: { - labelLong: 'Baltic (Windows 1257)', - labelShort: 'Windows 1257', - order: 13 - }, - iso88594: { - labelLong: 'Baltic (ISO 8859-4)', - labelShort: 'ISO 8859-4', - order: 14 - }, - iso885914: { - labelLong: 'Celtic (ISO 8859-14)', - labelShort: 'ISO 8859-14', - order: 15 - }, - windows1250: { - labelLong: 'Central European (Windows 1250)', - labelShort: 'Windows 1250', - order: 16 - }, - iso88592: { - labelLong: 'Central European (ISO 8859-2)', - labelShort: 'ISO 8859-2', - order: 17 - }, - cp852: { - labelLong: 'Central European (CP 852)', - labelShort: 'CP 852', - order: 18 - }, - windows1251: { - labelLong: 'Cyrillic (Windows 1251)', - labelShort: 'Windows 1251', - order: 19 - }, - cp866: { - labelLong: 'Cyrillic (CP 866)', - labelShort: 'CP 866', - order: 20 - }, - iso88595: { - labelLong: 'Cyrillic (ISO 8859-5)', - labelShort: 'ISO 8859-5', - order: 21 - }, - koi8r: { - labelLong: 'Cyrillic (KOI8-R)', - labelShort: 'KOI8-R', - order: 22 - }, - koi8u: { - labelLong: 'Cyrillic (KOI8-U)', - labelShort: 'KOI8-U', - order: 23 - }, - iso885913: { - labelLong: 'Estonian (ISO 8859-13)', - labelShort: 'ISO 8859-13', - order: 24 - }, - windows1253: { - labelLong: 'Greek (Windows 1253)', - labelShort: 'Windows 1253', - order: 25 - }, - iso88597: { - labelLong: 'Greek (ISO 8859-7)', - labelShort: 'ISO 8859-7', - order: 26 - }, - windows1255: { - labelLong: 'Hebrew (Windows 1255)', - labelShort: 'Windows 1255', - order: 27 - }, - iso88598: { - labelLong: 'Hebrew (ISO 8859-8)', - labelShort: 'ISO 8859-8', - order: 28 - }, - iso885910: { - labelLong: 'Nordic (ISO 8859-10)', - labelShort: 'ISO 8859-10', - order: 29 - }, - iso885916: { - labelLong: 'Romanian (ISO 8859-16)', - labelShort: 'ISO 8859-16', - order: 30 - }, - windows1254: { - labelLong: 'Turkish (Windows 1254)', - labelShort: 'Windows 1254', - order: 31 - }, - iso88599: { - labelLong: 'Turkish (ISO 8859-9)', - labelShort: 'ISO 8859-9', - order: 32 - }, - windows1258: { - labelLong: 'Vietnamese (Windows 1258)', - labelShort: 'Windows 1258', - order: 33 - }, - gbk: { - labelLong: 'Simplified Chinese (GBK)', - labelShort: 'GBK', - order: 34 - }, - gb18030: { - labelLong: 'Simplified Chinese (GB18030)', - labelShort: 'GB18030', - order: 35 - }, - cp950: { - labelLong: 'Traditional Chinese (Big5)', - labelShort: 'Big5', - order: 36 - }, - big5hkscs: { - labelLong: 'Traditional Chinese (Big5-HKSCS)', - labelShort: 'Big5-HKSCS', - order: 37 - }, - shiftjis: { - labelLong: 'Japanese (Shift JIS)', - labelShort: 'Shift JIS', - order: 38 - }, - eucjp: { - labelLong: 'Japanese (EUC-JP)', - labelShort: 'EUC-JP', - order: 39 - }, - euckr: { - labelLong: 'Korean (EUC-KR)', - labelShort: 'EUC-KR', - order: 40 - }, - windows874: { - labelLong: 'Thai (Windows 874)', - labelShort: 'Windows 874', - order: 41 - }, - iso885911: { - labelLong: 'Latin/Thai (ISO 8859-11)', - labelShort: 'ISO 8859-11', - order: 42 - }, - koi8ru: { - labelLong: 'Cyrillic (KOI8-RU)', - labelShort: 'KOI8-RU', - order: 43 - }, - koi8t: { - labelLong: 'Tajik (KOI8-T)', - labelShort: 'KOI8-T', - order: 44 - }, - gb2312: { - labelLong: 'Simplified Chinese (GB 2312)', - labelShort: 'GB 2312', - order: 45 - }, - cp865: { - labelLong: 'Nordic DOS (CP 865)', - labelShort: 'CP 865', - order: 46 - }, - cp850: { - labelLong: 'Western European DOS (CP 850)', - labelShort: 'CP 850', - order: 47 - } -}; diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts index f110a6b49f7..37930430364 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts @@ -11,7 +11,6 @@ import * as streams from 'vs/base/common/stream'; import * as iconv from 'iconv-lite-umd'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer'; -import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { isWindows } from 'vs/base/common/platform'; export async function detectEncodingByBOM(file: string): Promise { @@ -427,7 +426,7 @@ suite('Encoding', () => { }); test('encodingExists', async function () { - for (const enc in SUPPORTED_ENCODINGS) { + for (const enc in encoding.SUPPORTED_ENCODINGS) { if (enc === encoding.UTF8_with_bom) { continue; // skip over encodings from us } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 820fc5ca881..6c446ffc535 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -302,16 +302,16 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat const result = await this.dialogService.show( Severity.Info, - localize('Replace or Merge', "Replace or Merge"), + localize('preferences sync', "Preferences Sync"), [ localize('merge', "Merge"), localize('replace local', "Replace Local"), - localize('sync manually', "Sync Manually"), + localize('sync manually', "Sync Manually..."), localize('cancel', "Cancel"), ], { cancelId: 3, - detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to replace or merge with the synced data?"), + detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to replace or merge with your data in the cloud or sync manually?"), } ); switch (result.choice) { @@ -337,7 +337,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat await this.waitForActiveSyncViews(); await this.viewsService.openView(MANUAL_SYNC_VIEW_ID); - const completed = await Event.toPromise(this.userDataSyncPreview.onDidCompleteManualSync); + const error = await Event.toPromise(this.userDataSyncPreview.onDidCompleteManualSync); this.userDataSyncPreview.unsetManualSyncPreview(); this.manualSyncViewEnablementContext.set(false); @@ -348,15 +348,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this.viewsService.closeViewContainer(viewContainer!.id); } - if (!completed) { - throw canceled(); + if (error) { + throw error; } } async resetSyncedData(): Promise { const result = await this.dialogService.confirm({ - message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."), - title: localize('reset title', "Reset Synced Data"), + message: localize('reset', "This will clear your data in the cloud and stop sync on all your devices."), + title: localize('reset title', "Clear"), type: 'info', primaryButton: localize('reset button', "Reset"), }); @@ -567,7 +567,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { private _onDidChangeConflicts = this._register(new Emitter>()); readonly onDidChangeConflicts = this._onDidChangeConflicts.event; - private _onDidCompleteManualSync = this._register(new Emitter()); + private _onDidCompleteManualSync = this._register(new Emitter()); readonly onDidCompleteManualSync = this._onDidCompleteManualSync.event; private manualSync: { preview: [SyncResource, ISyncResourcePreview][], task: IManualSyncTask, disposables: DisposableStore } | undefined; @@ -593,7 +593,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { this.updateResources(); } - async accept(syncResource: SyncResource, resource: URI, content: string): Promise { + async accept(syncResource: SyncResource, resource: URI, content: string | null): Promise { if (this.manualSync) { const syncPreview = await this.manualSync.task.accept(resource, content); this.updatePreview(syncPreview); @@ -623,10 +623,16 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { throw new Error('Can apply only while syncing manually'); } - const syncPreview = await this.manualSync.task.apply(); - this.updatePreview(syncPreview); - if (!this._resources.length) { - this._onDidCompleteManualSync.fire(true); + try { + const syncPreview = await this.manualSync.task.apply(); + this.updatePreview(syncPreview); + if (!this._resources.length) { + this._onDidCompleteManualSync.fire(undefined); + } + } catch (error) { + await this.manualSync.task.stop(); + this.updatePreview([]); + this._onDidCompleteManualSync.fire(error); } } @@ -636,7 +642,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { } await this.manualSync.task.stop(); this.updatePreview([]); - this._onDidCompleteManualSync.fire(false); + this._onDidCompleteManualSync.fire(canceled()); } async pull(): Promise { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 1bac7f1a6c4..f5d0e9b2d49 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -20,7 +20,7 @@ export interface IUserDataSyncPreview { readonly onDidChangeResources: Event>; readonly resources: ReadonlyArray; - accept(syncResource: SyncResource, resource: URI, content: string): Promise; + accept(syncResource: SyncResource, resource: URI, content: string | null): Promise; merge(resource?: URI): Promise; discard(resource?: URI): Promise; pull(): Promise; diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index dbe45d1ab02..8e74f8b5f48 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -102,7 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('hasLocalData'); } - accept(syncResource: SyncResource, resource: URI, content: string, apply: boolean): Promise { + accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise { return this.channel.call('accept', [syncResource, resource, content, apply]); } @@ -186,7 +186,7 @@ class ManualSyncTask implements IManualSyncTask { return this.deserializePreviews(previews); } - async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> { + async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('accept', [resource, content]); return this.deserializePreviews(previews); } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index eb0221ab529..e2211240cd0 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -194,6 +194,8 @@ export class TestElectronService implements IElectronService { async showItemInFolder(path: string): Promise { } async setRepresentedFilename(path: string): Promise { } async isAdmin(): Promise { return false; } + async getTotalMem(): Promise { return 0; } + async killProcess(): Promise { } async setDocumentEdited(edited: boolean): Promise { } async openExternal(url: string): Promise { return false; } async updateTouchBar(): Promise { } @@ -213,7 +215,6 @@ export class TestElectronService implements IElectronService { async exit(code: number): Promise { } async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise { } async toggleDevTools(): Promise { } - async startCrashReporter(options: Electron.CrashReporterStartOptions): Promise { } async resolveProxy(url: string): Promise { return undefined; } async readClipboardText(type?: 'selection' | 'clipboard' | undefined): Promise { return ''; } async writeClipboardText(text: string, type?: 'selection' | 'clipboard' | undefined): Promise { } diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 319fb686589..9ce4feaa1e8 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -1111,9 +1111,9 @@ locate-path@^3.0.0: path-exists "^3.0.0" lodash@^4.16.4, lodash@^4.17.15, lodash@^4.5.1: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@2.2.0: version "2.2.0" diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 6d1c7653799..e6a3ac3b268 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -12,6 +12,10 @@ const events = require('events'); // const MochaJUnitReporter = require('mocha-junit-reporter'); const url = require('url'); +// Disable render process reuse, we still have +// non-context aware native modules in the renderer. +app.allowRendererProcessReuse = false; + const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec'; const optimist = require('optimist') @@ -112,14 +116,13 @@ app.on('ready', () => { width: 800, show: false, webPreferences: { - backgroundThrottling: false, - nodeIntegration: true, - webSecurity: false, - webviewTag: true, preload: path.join(__dirname, '..', '..', '..', 'src', 'vs', 'base', 'parts', 'sandbox', 'electron-browser', 'preload.js'), // ensure similar environment as VSCode as tests may depend on this + nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, - nativeWindowOpen: true + spellcheck: false, + nativeWindowOpen: true, + webviewTag: true } }); diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 734c0837841..e2a4b435690 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -32,7 +32,7 @@ function initLoader(opts) { nodeRequire: require, nodeMain: __filename, catchError: true, - baseUrl: bootstrap.uriFromPath(path.join(__dirname, '../../../src')), + baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../src')), paths: { 'vs': `../${outdir}/vs`, 'lib': `../${outdir}/lib`, diff --git a/yarn.lock b/yarn.lock index c09a8d400b0..543779f3d52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2737,10 +2737,10 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@8.3.3: - version "8.3.3" - resolved "https://registry.yarnpkg.com/electron/-/electron-8.3.3.tgz#8ce07dcbafa097d00b94d1dd58a2a3b30fe6f803" - integrity sha512-/LGnjnE9BQzkn2VpjflLi7jpQxYIp+maqmiDPy6ww76hkQvt/LJ991ewdHpfLR4or3VqzPIu+AK+ZJrTlDAWyw== +electron@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-9.1.0.tgz#ca77600c9e4cd591298c340e013384114d3d8d05" + integrity sha512-VRAF8KX1m0py9I9sf0kw1kWfeC87mlscfFcbcRdLBsNJ44/GrJhi3+E8rKbpHUeZNQxsPaVA5Zu5Lxb6dV/scQ== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" @@ -5654,25 +5654,10 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.15.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= - -lodash@^4.17.10: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== - -lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== - -lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== lolex@1.3.2: version "1.3.2"