diff --git a/.eslintrc.json b/.eslintrc.json index c5034298ff..090e59c5d5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -764,6 +764,18 @@ "*" // node modules ] }, + { + "target": "**/{vs,sql}/code/browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/{vs,sql}/base/**/{common,browser}/**", + "**/{vs,sql}/base/parts/**/{common,browser}/**", + "**/{vs,sql}/platform/**/{common,browser}/**", + "**/{vs,sql}/code/**/{common,browser}/**", + "**/{vs,sql}/workbench/workbench.web.api" + ] + }, { "target": "**/{vs,sql}/code/node/**", "restrictions": [ @@ -834,6 +846,18 @@ "**/{vs,sql}/workbench/workbench.common.main" ] }, + { + "target": "**/src/{vs,sql}/workbench/workbench.web.api.ts", + "restrictions": [ + "vs/nls", + "**/{vs,sql}/base/**/{common,browser}/**", + "**/{vs,sql}/base/parts/**/{common,browser}/**", + "**/{vs,sql}/platform/**/{common,browser}/**", + "**/{vs,sql}/editor/**", + "**/{vs,sql}/workbench/**/{common,browser}/**", + "**/{vs,sql}/workbench/workbench.web.main" + ] + }, { "target": "**/src/{vs,sql}/workbench/workbench.sandbox.main.ts", "restrictions": [ diff --git a/.yarnrc b/.yarnrc index 4c5125d892..5119ded102 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 e758389603..d6f61a7aec 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@3 # {{SQL CARBON EDIT}} update version inputs: diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 9c5b047ceb..ae6dae321b 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 a286bf7ef8..f26d5db267 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-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/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index ff2b7065cb..0b825b7438 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 cb871f65bd..2f6482bd76 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@3 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index 485f8dcfba..258f87ea3d 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 5d7bccf467..dbd0621a27 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 a530499b31..39c39e86c9 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 db6524be03..ab0dbb932c 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 b73cd04a96..10b6aa4e16 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 2641830a41..49dfc9ced8 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 0c338203b4..7f4907aa2d 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 0e4045639e..5470b8e0de 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@3 # {{SQL CARBON EDIT}} update version inputs: diff --git a/build/azure-pipelines/win32/product-build-win32-arm64.yml b/build/azure-pipelines/win32/product-build-win32-arm64.yml index 01be34aa9a..ecb50ad678 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 fb4f305257..c233d5c6d2 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 06dfe2b783..6b43542afb 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -97,6 +97,7 @@ const vscodeResources = [ 'out-build/sql/media/icons/*.svg', 'out-build/sql/workbench/parts/notebook/media/**/*.svg', 'out-build/sql/setup.js', // {{SQL CARBON EDIT}} end + '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 9bba404c24..ceef664b85 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/package.json b/build/package.json index 044f4c5fa1..23d1948000 100644 --- a/build/package.json +++ b/build/package.json @@ -49,7 +49,7 @@ "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "terser": "4.3.8", - "typescript": "^4.0.0-dev.20200708", + "typescript": "^4.0.0-dev.20200715", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.6.0", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index fefcc4c40a..01a79de5d4 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3539,10 +3539,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^4.0.0-dev.20200708: - version "4.0.0-dev.20200708" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200708.tgz#9cf526945590456f34b158b180a6f8a2e0b57306" - integrity sha512-A1brZPJRwkm2pxOSTIOVqLvrhF1swfLvDAgN+jRP/yUWNn+0OqbfunuY/jvRbtkEP4/AWZ+M2P625Y+JDN/j2A== +typescript@^4.0.0-dev.20200715: + version "4.0.0-dev.20200715" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200715.tgz#d65961a5a6f13fde95a6f4db5f5946f15e4c59bc" + integrity sha512-gmPXoWktfXeutmWTM6el9U4vIn5kqOHGI1OESSOhPtLWrxodKqLfFuMygQtOUTtGjKLFQRFAJhHEwUhHZNOURA== typical@^4.0.0: version "4.0.0" diff --git a/cgmanifest.json b/cgmanifest.json index 1fb78691e7..29afebf6b0 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/big-data-cluster/yarn.lock b/extensions/big-data-cluster/yarn.lock index 42edbe476a..41e278a1dd 100644 --- a/extensions/big-data-cluster/yarn.lock +++ b/extensions/big-data-cluster/yarn.lock @@ -264,9 +264,9 @@ glob@7.1.2: path-is-absolute "^1.0.0" glob@^7.1.2: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -533,9 +533,9 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^5.4.1: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== source-map-support@^0.5.0: version "0.5.12" diff --git a/extensions/extension-editing/extension.webpack.config.js b/extensions/extension-editing/extension.webpack.config.js index 9fcce7e999..80f8cff5d5 100644 --- a/extensions/extension-editing/extension.webpack.config.js +++ b/extensions/extension-editing/extension.webpack.config.js @@ -14,6 +14,9 @@ module.exports = withDefaults({ entry: { extension: './src/extensionEditingMain.ts', }, + output: { + filename: 'extensionEditingMain.js' + }, externals: { '../../../product.json': 'commonjs ../../../product.json', 'typescript': 'commonjs typescript' diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 0a4c850d7a..c4189e6b7e 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/github.ts b/extensions/github-authentication/src/github.ts index 55376dcdfa..838c1de0b8 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -141,7 +141,12 @@ export class GitHubAuthenticationProvider { private async tokenToSession(token: string, scopes: string[]): Promise { const userInfo = await this._githubServer.getUserInfo(token); - return new vscode.AuthenticationSession(uuid(), token, { label: userInfo.accountName, id: userInfo.id }, scopes); + return { + id: uuid(), + accessToken: token, + account: { label: userInfo.accountName, id: userInfo.id }, + scopes + }; } private async setToken(session: vscode.AuthenticationSession): Promise { diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index ca174b92ad..5e61d996f1 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/github/package.json b/extensions/github/package.json index 6535ac92a3..464453d399 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -57,7 +57,7 @@ "watch": "gulp watch-extension:github" }, "dependencies": { - "@octokit/rest": "^17.9.1", + "@octokit/rest": "^18.0.1", "tunnel": "^0.0.6", "vscode-nls": "^4.1.2" }, diff --git a/extensions/github/src/auth.ts b/extensions/github/src/auth.ts index 49b64555a6..945e95b324 100644 --- a/extensions/github/src/auth.ts +++ b/extensions/github/src/auth.ts @@ -34,10 +34,12 @@ let _octokit: Promise | undefined; export function getOctokit(): Promise { if (!_octokit) { - _octokit = getSession().then(session => { + _octokit = getSession().then(async session => { const token = session.accessToken; const agent = getAgent(); + const { Octokit } = await import('@octokit/rest'); + return new Octokit({ request: { agent }, userAgent: 'GitHub VSCode', diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts index d9ee2cff3b..d062a031e9 100644 --- a/extensions/github/src/publish.ts +++ b/extensions/github/src/publish.ts @@ -46,7 +46,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) folder = pick.folder.uri; } - let quickpick = vscode.window.createQuickPick(); + let quickpick = vscode.window.createQuickPick(); quickpick.ignoreFocusOut = true; quickpick.placeholder = 'Repository Name'; @@ -60,6 +60,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) quickpick.busy = false; let repo: string | undefined; + let isPrivate: boolean; const onDidChangeValue = async () => { const sanitizedRepo = sanitizeRepositoryName(quickpick.value); @@ -67,7 +68,10 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) if (!sanitizedRepo) { quickpick.items = []; } else { - quickpick.items = [{ label: `$(repo) Publish to GitHub private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo }]; + quickpick.items = [ + { label: `$(repo) Publish to GitHub private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo, isPrivate: true }, + { label: `$(repo) Publish to GitHub public repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo, isPrivate: false }, + ]; } }; @@ -79,6 +83,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) listener.dispose(); repo = pick?.repo; + isPrivate = pick?.isPrivate ?? true; if (repo) { try { @@ -145,11 +150,11 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) } const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => { - progress.report({ message: 'Publishing to GitHub private repository', increment: 25 }); + progress.report({ message: `Publishing to GitHub ${isPrivate ? 'private' : 'public'} repository`, increment: 25 }); const res = await octokit.repos.createForAuthenticatedUser({ name: repo!, - private: true + private: isPrivate }); const createdGithubRepository = res.data; diff --git a/extensions/github/yarn.lock b/extensions/github/yarn.lock index 6e8d2a7cc4..a9e0ba38b6 100644 --- a/extensions/github/yarn.lock +++ b/extensions/github/yarn.lock @@ -3,113 +3,106 @@ "@octokit/auth-token@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.0.tgz#b64178975218b99e4dfe948253f0673cbbb59d9f" - integrity sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg== + version "2.4.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" + integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ== dependencies: - "@octokit/types" "^2.0.0" + "@octokit/types" "^5.0.0" -"@octokit/core@^2.4.3": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-2.5.0.tgz#4706258893a7ac6ab35d58d2fb9f2d2ba19a41a5" - integrity sha512-uvzmkemQrBgD8xuGbjhxzJN1darJk9L2cS+M99cHrDG2jlSVpxNJVhoV86cXdYBqdHCc9Z995uLCczaaHIYA6Q== +"@octokit/core@^3.0.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.1.tgz#1856745aa8fb154cf1544a2a1b82586c809c5e66" + integrity sha512-cQ2HGrtyNJ1IBxpTP1U5m/FkMAJvgw7d2j1q3c9P0XUuYilEgF6e4naTpsgm4iVcQeOnccZlw7XHRIUBy0ymcg== dependencies: "@octokit/auth-token" "^2.4.0" "@octokit/graphql" "^4.3.1" "@octokit/request" "^5.4.0" - "@octokit/types" "^2.0.0" + "@octokit/types" "^5.0.0" before-after-hook "^2.1.0" - universal-user-agent "^5.0.0" + universal-user-agent "^6.0.0" "@octokit/endpoint@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.1.tgz#16d5c0e7a83e3a644d1ddbe8cded6c3d038d31d7" - integrity sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A== + version "6.0.4" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.4.tgz#da3eafdee1fabd6e5b6ca311efcba26f0dd99848" + integrity sha512-ZJHIsvsClEE+6LaZXskDvWIqD3Ao7+2gc66pRG5Ov4MQtMvCU9wGu1TItw9aGNmRuU9x3Fei1yb+uqGaQnm0nw== dependencies: - "@octokit/types" "^2.11.1" + "@octokit/types" "^5.0.0" is-plain-object "^3.0.0" - universal-user-agent "^5.0.0" + universal-user-agent "^6.0.0" "@octokit/graphql@^4.3.1": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.4.0.tgz#4540b48bbf796b837b311ba6ea5104760db530ca" - integrity sha512-Du3hAaSROQ8EatmYoSAJjzAz3t79t9Opj/WY1zUgxVUGfIKn0AEjg+hlOLscF6fv6i/4y/CeUvsWgIfwMkTccw== + version "4.5.2" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.2.tgz#33021ebf94939cf47562823851ab11fe64392274" + integrity sha512-SpB/JGdB7bxRj8qowwfAXjMpICUYSJqRDj26MKJAryRQBqp/ZzARsaO2LEFWzDaps0FLQoPYVGppS0HQXkBhdg== dependencies: "@octokit/request" "^5.3.0" - "@octokit/types" "^2.0.0" - universal-user-agent "^5.0.0" + "@octokit/types" "^5.0.0" + universal-user-agent "^6.0.0" "@octokit/plugin-paginate-rest@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.0.tgz#9ae0c14c1b90ec0d96d2ef1b44706b4505a91cee" - integrity sha512-KoNxC3PLNar8UJwR+1VMQOw2IoOrrFdo5YOiDKnBhpVbKpw+zkBKNMNKwM44UWL25Vkn0Sl3nYIEGKY+gW5ebw== + version "2.2.3" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.3.tgz#a6ad4377e7e7832fb4bdd9d421e600cb7640ac27" + integrity sha512-eKTs91wXnJH8Yicwa30jz6DF50kAh7vkcqCQ9D7/tvBAP5KKkg6I2nNof8Mp/65G0Arjsb4QcOJcIEQY+rK1Rg== dependencies: - "@octokit/types" "^2.12.1" + "@octokit/types" "^5.0.0" "@octokit/plugin-request-log@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== -"@octokit/plugin-rest-endpoint-methods@^3.11.1": - version "3.12.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.12.0.tgz#313938ece4267e98687179cdbc32bc09e06f2379" - integrity sha512-cpUTZR0V2B8lz351oKLXx01BxBF3sMOhvm0glEMIS9XWSpkHCeQDPLPh8TQmepY8e+AhhgWxuWdn1PLDPQlTpw== +"@octokit/plugin-rest-endpoint-methods@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.1.0.tgz#338c568177c4d4d753f9525af88b29cd0f091734" + integrity sha512-zbRTjm+xplSNlixotTVMvLJe8aRogUXS+r37wZK5EjLsNYH4j02K5XLMOWyYaSS4AJEZtPmzCcOcui4VzVGq+A== dependencies: - "@octokit/types" "^4.0.0" + "@octokit/types" "^5.1.0" deprecation "^2.3.1" "@octokit/request-error@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.0.tgz#94ca7293373654400fbb2995f377f9473e00834b" - integrity sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw== + version "2.0.2" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" + integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== dependencies: - "@octokit/types" "^2.0.0" + "@octokit/types" "^5.0.1" deprecation "^2.0.0" once "^1.4.0" "@octokit/request@^5.3.0", "@octokit/request@^5.4.0": - version "5.4.2" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.2.tgz#74f8e5bbd39dc738a1b127629791f8ad1b3193ee" - integrity sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw== + version "5.4.6" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.6.tgz#e8cc8d4cfc654d30428ea92aaa62168fd5ead7eb" + integrity sha512-9r8Sn4CvqFI9LDLHl9P17EZHwj3ehwQnTpTE+LEneb0VBBqSiI/VS4rWIBfBhDrDs/aIGEGZRSB0QWAck8u+2g== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" - "@octokit/types" "^2.11.1" + "@octokit/types" "^5.0.0" deprecation "^2.0.0" is-plain-object "^3.0.0" node-fetch "^2.3.0" once "^1.4.0" - universal-user-agent "^5.0.0" + universal-user-agent "^6.0.0" -"@octokit/rest@^17.9.1": - version "17.9.1" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-17.9.1.tgz#ffa31614b2a2330fac82dcff8bb5e88c63fa7cbb" - integrity sha512-TCTqCMNs21ToN5rIx15sIETuR19WsAy9wI4GnSVY8yZxeOKzeQs8uvIeijy8Gb6DbcYGXDf+xJ1MNDePPss0DA== +"@octokit/rest@^18.0.1": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.1.tgz#46ee234770c5ff4c646f7e18708c56b6d7fa3c66" + integrity sha512-KLlJpgsJx88OZ0VLBH3gvUK4sfcXjr/nE0Qzyoe76dNqMzDzkSmmvILF3f2XviGgrzuP6Ie0ay/QX478Vrpn9A== dependencies: - "@octokit/core" "^2.4.3" + "@octokit/core" "^3.0.0" "@octokit/plugin-paginate-rest" "^2.2.0" "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "^3.11.1" + "@octokit/plugin-rest-endpoint-methods" "4.1.0" -"@octokit/types@^2.0.0", "@octokit/types@^2.11.1", "@octokit/types@^2.12.1": - version "2.16.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" - integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q== - dependencies: - "@types/node" ">= 8" - -"@octokit/types@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-4.0.0.tgz#57425d275d33b02922e8822eabf57466ef243969" - integrity sha512-wbjL8HhCLdBOAmvPJPkMqF4bf6AWzsas78I7+slJt5LAjuAL+kTlWtXHr2V9VnOuEFItZdzfgFTpMjSBkFeVZg== +"@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.1.0.tgz#4377a3f39edad3e60753fb5c3c310756f1ded57f" + integrity sha512-OFxUBgrEllAbdEmWp/wNmKIu5EuumKHG4sgy56vjZ8lXPgMhF05c76hmulfOdFHHYRpPj49ygOZJ8wgVsPecuA== dependencies: "@types/node" ">= 8" "@types/node@>= 8": - version "14.0.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.1.tgz#5d93e0a099cd0acd5ef3d5bde3c086e1f49ff68c" - integrity sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA== + version "14.0.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806" + integrity sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw== "@types/node@^10.12.21": version "10.17.14" @@ -121,184 +114,43 @@ before-after-hook@^2.1.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - is-plain-object@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928" - integrity sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg== - dependencies: - isobject "^4.0.0" - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" - integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== - -macos-release@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" - integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== node-fetch@^2.3.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -once@^1.3.1, once@^1.4.0: +once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -os-name@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - tunnel@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== -universal-user-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-5.0.0.tgz#a3182aa758069bf0e79952570ca757de3579c1d9" - integrity sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q== - dependencies: - os-name "^3.1.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== vscode-nls@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -windows-release@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.0.tgz#dce167e9f8be733f21c849ebd4d03fe66b29b9f0" - integrity sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ== - dependencies: - execa "^1.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index 12b9b68f88..4075748e35 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -1 +1 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),i="code-line";function r(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const c=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(i)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function s(e){const t=Math.floor(e),n=c();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function a(e){const t=c(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const r=t[i],s=u(r);if(i>=1&&s.top>n){return{previous:t[o],next:r}}return i>1&&in?{previous:r,next:t[i+1]}:{previous:r}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${i}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=a,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=u(t),c=r.top;if(n&&n.line!==t.line){i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c)}else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=a(e);if(t){const o=u(t),i=e-window.scrollY-o.top;if(n){const e=i/(u(n).top-o.top);return r(t.line+e*(n.line-t.line))}{const e=i/o.height;return r(t.line+e)}}return null},t.getLineElementForFragment=function(e){return c().find(t=>t.element.id===e)}},function(e,t,n){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});const o=n(7),i=n(8),r=n(9),c=n(2),s=n(0),a=n(10);let u=!0;const l=new o.ActiveLineMarker,f=s.getSettings(),d=acquireVsCodeApi(),m={...d.getState(),...s.getData("data-state")};d.setState(m);const p=r.createPosterForVsCode(d);window.cspAlerter.setPoster(p),window.styleLoadingMonitor.setPoster(p),window.onload=()=>{h()},i.onceDocumentLoaded(()=>{const t=m.scrollProgress;"number"!=typeof t||f.fragment?f.scrollPreviewWithEditor&&e(()=>{if(f.fragment){m.fragment=void 0,d.setState(m);const e=c.getLineElementForFragment(f.fragment);e&&(u=!0,c.scrollToRevealSourceLine(e.line))}else isNaN(f.line)||(u=!0,c.scrollToRevealSourceLine(f.line))}):e(()=>{u=!0,window.scrollTo(0,t*document.body.clientHeight)})});const g=(()=>{const e=a(e=>{u=!0,c.scrollToRevealSourceLine(e)},50);return t=>{isNaN(t)||(m.line=t,e(t))}})();let h=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,y(),h()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":g(e.data.line)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=c.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||p.postMessage("didClick",{line:Math.floor(n)})});const v=["http:","https:","mailto:","vscode:","vscode-insiders:"];function y(){m.scrollProgress=window.scrollY/document.body.clientHeight,d.setState(m)}document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(v.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(p.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(y(),u)u=!1;else{const e=c.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||p.postMessage("revealLine",{line:e})}},50))}).call(this,n(4).setImmediate)},function(e,t,n){(function(e){var o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(5),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(1))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var o,i,r,c,s,a=1,u={},l=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?o=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){p(e.data)},o=function(e){r.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,o=function(e){var t=f.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):o=function(e){setTimeout(p,0,e)}:(c="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(c)&&p(+t.data.slice(c.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),o=function(t){e.postMessage(c+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;nnew class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,c=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),m=Object.prototype.toString,p=Math.max,g=Math.min,h=function(){return d.Date.now()};function v(e,t,o){var i,r,c,s,a,u,l=0,f=!1,d=!1,m=!0;if("function"!=typeof e)throw new TypeError(n);function v(t){var n=i,o=r;return i=r=void 0,l=t,s=e.apply(o,n)}function b(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=c}function T(){var e=h();if(b(e))return E(e);a=setTimeout(T,function(e){var n=t-(e-u);return d?g(n,c-(e-l)):n}(e))}function E(e){return a=void 0,m&&i?v(e):(i=r=void 0,s)}function _(){var e=h(),n=b(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(T,t),f?v(e):s}(u);if(d)return a=setTimeout(T,t),v(u)}return void 0===a&&(a=setTimeout(T,t)),s}return t=w(t)||0,y(o)&&(f=!!o.leading,c=(d="maxWait"in o)?p(w(o.maxWait)||0,t):c,m="trailing"in o?!!o.trailing:m),_.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},_.flush=function(){return void 0===a?s:E(h())},_}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&m.call(e)==i}(e))return o;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):c.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return y(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),v(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(1))}]); \ No newline at end of file +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),i="code-line";function r(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const c=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(i)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function s(e){const t=Math.floor(e),n=c();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function a(e){const t=c(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const r=t[i],s=u(r);if(i>=1&&s.top>n){return{previous:t[o],next:r}}return i>1&&in?{previous:r,next:t[i+1]}:{previous:r}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${i}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=a,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=u(t),c=r.top;if(n&&n.line!==t.line){i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c)}else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=a(e);if(t){const o=u(t),i=e-window.scrollY-o.top;if(n){const e=i/(u(n).top-o.top);return r(t.line+e*(n.line-t.line))}{const e=i/o.height;return r(t.line+e)}}return null},t.getLineElementForFragment=function(e){return c().find(t=>t.element.id===e)}},function(e,t,n){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});const o=n(7),i=n(8),r=n(9),c=n(2),s=n(0),a=n(10);let u=!0;const l=new o.ActiveLineMarker,f=s.getSettings(),d=acquireVsCodeApi(),m=d.getState(),p={..."object"==typeof m?m:{},...s.getData("data-state")};d.setState(p);const g=r.createPosterForVsCode(d);window.cspAlerter.setPoster(g),window.styleLoadingMonitor.setPoster(g),window.onload=()=>{v()},i.onceDocumentLoaded(()=>{const t=p.scrollProgress;"number"!=typeof t||f.fragment?f.scrollPreviewWithEditor&&e(()=>{if(f.fragment){p.fragment=void 0,d.setState(p);const e=c.getLineElementForFragment(f.fragment);e&&(u=!0,c.scrollToRevealSourceLine(e.line))}else isNaN(f.line)||(u=!0,c.scrollToRevealSourceLine(f.line))}):e(()=>{u=!0,window.scrollTo(0,t*document.body.clientHeight)})});const h=(()=>{const e=a(e=>{u=!0,c.scrollToRevealSourceLine(e)},50);return t=>{isNaN(t)||(p.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,w(),v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":h(e.data.line)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=c.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||g.postMessage("didClick",{line:Math.floor(n)})});const y=["http:","https:","mailto:","vscode:","vscode-insiders:"];function w(){p.scrollProgress=window.scrollY/document.body.clientHeight,d.setState(p)}document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(y.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(g.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(w(),u)u=!1;else{const e=c.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||g.postMessage("revealLine",{line:e})}},50))}).call(this,n(4).setImmediate)},function(e,t,n){(function(e){var o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(5),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(1))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var o,i,r,c,s,a=1,u={},l=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?o=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){p(e.data)},o=function(e){r.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,o=function(e){var t=f.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):o=function(e){setTimeout(p,0,e)}:(c="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(c)&&p(+t.data.slice(c.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),o=function(t){e.postMessage(c+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;nnew class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,c=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),m=Object.prototype.toString,p=Math.max,g=Math.min,h=function(){return d.Date.now()};function v(e,t,o){var i,r,c,s,a,u,l=0,f=!1,d=!1,m=!0;if("function"!=typeof e)throw new TypeError(n);function v(t){var n=i,o=r;return i=r=void 0,l=t,s=e.apply(o,n)}function b(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=c}function T(){var e=h();if(b(e))return E(e);a=setTimeout(T,function(e){var n=t-(e-u);return d?g(n,c-(e-l)):n}(e))}function E(e){return a=void 0,m&&i?v(e):(i=r=void 0,s)}function _(){var e=h(),n=b(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(T,t),f?v(e):s}(u);if(d)return a=setTimeout(T,t),v(u)}return void 0===a&&(a=setTimeout(T,t)),s}return t=w(t)||0,y(o)&&(f=!!o.leading,c=(d="maxWait"in o)?p(w(o.maxWait)||0,t):c,m="trailing"in o?!!o.trailing:m),_.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},_.flush=function(){return void 0===a?s:E(h())},_}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&m.call(e)==i}(e))return o;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):c.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return y(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),v(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(1))}]); \ No newline at end of file diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index c10a0198f2..b43eb8e720 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -131,6 +131,10 @@ p { margin-bottom: 1.5em; } +li > p { + margin-bottom: 0; +} + /* don't space 2 paragraphs too far apart */ p + p { margin-top: -0.8em; diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index c09e811854..2bffee4656 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -18,7 +18,13 @@ const settings = getSettings(); const vscode = acquireVsCodeApi(); -const state = { ...vscode.getState(), ...getData('data-state') }; +const originalState = vscode.getState(); + +const state = { + ...(typeof originalState === 'object' ? originalState : {}), + ...getData('data-state') +}; + // Make sure to sync VS Code state here vscode.setState(state); diff --git a/extensions/microsoft-authentication/extension-browser.webpack.config.js b/extensions/microsoft-authentication/extension-browser.webpack.config.js new file mode 100644 index 0000000000..4f362dc3d6 --- /dev/null +++ b/extensions/microsoft-authentication/extension-browser.webpack.config.js @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const path = require('path'); +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + node: false, + entry: { + extension: './src/extension.ts', + }, + externals: { + 'keytar': 'commonjs keytar' + }, + 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'), + 'stream': path.resolve(__dirname, 'node_modules/stream/index.js'), + 'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js') + } + } +}); diff --git a/extensions/microsoft-authentication/extension.webpack.config.js b/extensions/microsoft-authentication/extension.webpack.config.js index aba62f39e2..4e4846c4f4 100644 --- a/extensions/microsoft-authentication/extension.webpack.config.js +++ b/extensions/microsoft-authentication/extension.webpack.config.js @@ -7,6 +7,7 @@ 'use strict'; +const path = require('path'); const withDefaults = require('../shared.webpack.config'); module.exports = withDefaults({ diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index dde513aa5e..0794d50127 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -12,24 +12,35 @@ ], "enableProposedApi": true, "activationEvents": [ - "*" + "*", + "onAuthenticationRequest:microsoft" ], "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", + "browser": "./dist/browser/extension.js", "scripts": { "vscode:prepublish": "npm run compile", "compile": "gulp compile-extension:microsoft-authentication", - "watch": "gulp watch-extension:microsoft-authentication" + "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", + "watch": "gulp watch-extension:microsoft-authentication", + "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "devDependencies": { - "typescript": "^3.7.4", - "tslint": "^5.12.1", - "@types/node": "^10.12.21", "@types/keytar": "^4.0.1", - "@types/uuid": "^3.4.6" + "@types/node": "^10.12.21", + "@types/node-fetch": "^2.5.7", + "@types/randombytes": "^2.0.0", + "@types/sha.js": "^2.4.0", + "@types/uuid": "^8.0.0", + "typescript": "^3.7.4" }, "dependencies": { - "uuid": "^3.3.3", + "buffer": "^5.6.0", + "node-fetch": "^2.6.0", + "randombytes": "github:rmacfarlane/randombytes#b28d4ecee46262801ea09f15fa1f1513a05c5971", + "sha.js": "2.4.11", + "stream": "0.0.2", + "uuid": "^8.2.0", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.1.1" } diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 230e720db6..bead06efbd 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -3,15 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as crypto from 'crypto'; -import * as https from 'https'; +import * as randomBytes from 'randombytes'; import * as querystring from 'querystring'; import * as vscode from 'vscode'; -import * as uuid from 'uuid'; import { createServer, startServer } from './authServer'; + +import { v4 as uuid } from 'uuid'; import { keychain } from './keychain'; import Logger from './logger'; import { toBase64UrlEncoding } from './utils'; +import fetch from 'node-fetch'; +import { sha256 } from './env/node/sha256'; const redirectUrl = 'https://vscode-redirect.azurewebsites.net/'; const loginEndpointUrl = 'https://login.microsoftonline.com/'; @@ -21,7 +23,7 @@ const tenant = 'organizations'; interface IToken { accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined - expiresIn?: string; // How long access token is valid, in seconds + expiresIn?: number; // How long access token is valid, in seconds expiresAt?: number; // UNIX epoch time at which token will expire refreshToken: string; @@ -54,6 +56,15 @@ interface IStoredSession { } } +export interface ITokenResponse { + access_token: string; + expires_in: number; + ext_expires_in: number; + refresh_token: string; + scope: string; + token_type: string; +} + function parseQuery(uri: vscode.Uri) { return uri.query.split('&').reduce((prev: any, current) => { const queryString = current.split('='); @@ -208,7 +219,12 @@ export class AzureActiveDirectoryService { private async convertToSession(token: IToken): Promise { const resolvedToken = await this.resolveAccessToken(token); - return new vscode.AuthenticationSession(token.sessionId, resolvedToken, token.account, token.scope.split(' ')); + return { + id: token.sessionId, + accessToken: resolvedToken, + account: token.account, + scopes: token.scope.split(' ') + }; } private async resolveAccessToken(token: IToken): Promise { @@ -257,7 +273,7 @@ export class AzureActiveDirectoryService { return; } - const nonce = crypto.randomBytes(16).toString('base64'); + const nonce = randomBytes(16).toString('base64'); const { server, redirectPromise, codePromise } = createServer(nonce); let token: IToken | undefined; @@ -279,8 +295,8 @@ export class AzureActiveDirectoryService { const state = `${updatedPort},${encodeURIComponent(nonce)}`; - const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier)); const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; await redirectReq.res.writeHead(302, { Location: loginUrl }); @@ -341,14 +357,14 @@ export class AzureActiveDirectoryService { private async loginWithoutLocalServer(scope: string): Promise { const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`)); - const nonce = crypto.randomBytes(16).toString('base64'); + const nonce = randomBytes(16).toString('base64'); const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' ? 443 : 80); const callbackEnvironment = this.getCallbackEnvironment(callbackUri); const state = `${callbackEnvironment}${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`; const signInUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize`; let uri = vscode.Uri.parse(signInUrl); - const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier)); uri = uri.with({ query: `response_type=code&client_id=${encodeURIComponent(clientId)}&response_mode=query&redirect_uri=${redirectUrl}&state=${state}&scope=${scope}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` }); @@ -421,14 +437,13 @@ export class AzureActiveDirectoryService { onDidChangeSessions.fire({ added: [], removed: [token.sessionId], changed: [] }); } } - }, 1000 * (parseInt(token.expiresIn) - 30))); + }, 1000 * (token.expiresIn - 30))); } this.storeTokenData(); } - private getTokenFromResponse(buffer: Buffer[], scope: string, existingId?: string): IToken { - const json = JSON.parse(Buffer.concat(buffer).toString()); + private getTokenFromResponse(json: ITokenResponse, scope: string, existingId?: string): IToken { const claims = this.getTokenClaims(json.access_token); return { expiresIn: json.expires_in, @@ -445,60 +460,42 @@ export class AzureActiveDirectoryService { } private async exchangeCodeForToken(code: string, codeVerifier: string, scope: string): Promise { - return new Promise((resolve: (value: IToken) => void, reject) => { - Logger.info('Exchanging login code for token'); - try { - const postData = querystring.stringify({ - grant_type: 'authorization_code', - code: code, - client_id: clientId, - scope: scope, - code_verifier: codeVerifier, - redirect_uri: redirectUrl - }); + Logger.info('Exchanging login code for token'); + try { + const postData = querystring.stringify({ + grant_type: 'authorization_code', + code: code, + client_id: clientId, + scope: scope, + code_verifier: codeVerifier, + redirect_uri: redirectUrl + }); - const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`); + const result = await fetch(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length.toString() + }, + body: postData + }); - const post = https.request({ - host: tokenUrl.authority, - path: tokenUrl.path, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length - } - }, result => { - const buffer: Buffer[] = []; - result.on('data', (chunk: Buffer) => { - buffer.push(chunk); - }); - result.on('end', () => { - if (result.statusCode === 200) { - Logger.info('Exchanging login code for token success'); - resolve(this.getTokenFromResponse(buffer, scope)); - } else { - Logger.error('Exchanging login code for token failed'); - reject(new Error('Unable to login.')); - } - }); - }); - - post.write(postData); - - post.end(); - post.on('error', err => { - reject(err); - }); - - } catch (e) { - Logger.error(e.message); - reject(e); + if (result.ok) { + Logger.info('Exchanging login code for token success'); + const json = await result.json(); + return this.getTokenFromResponse(json, scope); + } else { + Logger.error('Exchanging login code for token failed'); + throw new Error('Unable to login.'); } - }); + } catch (e) { + Logger.error(e.message); + throw e; + } } private async refreshToken(refreshToken: string, scope: string, sessionId: string): Promise { - return new Promise((resolve: (value: IToken) => void, reject) => { + try { Logger.info('Refreshing token...'); const postData = querystring.stringify({ refresh_token: refreshToken, @@ -507,40 +504,29 @@ export class AzureActiveDirectoryService { scope: scope }); - const post = https.request({ - host: 'login.microsoftonline.com', - path: `/${tenant}/oauth2/v2.0/token`, + const result = await fetch(`https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length - } - }, result => { - const buffer: Buffer[] = []; - result.on('data', (chunk: Buffer) => { - buffer.push(chunk); - }); - result.on('end', async () => { - if (result.statusCode === 200) { - const token = this.getTokenFromResponse(buffer, scope, sessionId); - this.setToken(token, scope); - Logger.info('Token refresh success'); - resolve(token); - } else { - Logger.error('Refreshing token failed'); - reject(new Error('Refreshing token failed.')); - } - }); + 'Content-Length': postData.length.toString() + }, + body: postData }); - post.write(postData); - - post.end(); - post.on('error', err => { - Logger.error(err.message); - reject(new Error(REFRESH_NETWORK_FAILURE)); - }); - }); + if (result.ok) { + const json = await result.json(); + const token = this.getTokenFromResponse(json, scope, sessionId); + this.setToken(token, scope); + Logger.info('Token refresh success'); + return token; + } else { + Logger.error('Refreshing token failed'); + throw new Error('Refreshing token failed.'); + } + } catch (e) { + Logger.error('Refreshing token failed'); + throw e; + } } private clearSessionTimeout(sessionId: string): void { diff --git a/extensions/microsoft-authentication/src/authServer.ts b/extensions/microsoft-authentication/src/authServer.ts index e0d91f839c..ce1311c3a5 100644 --- a/extensions/microsoft-authentication/src/authServer.ts +++ b/extensions/microsoft-authentication/src/authServer.ts @@ -6,7 +6,6 @@ import * as http from 'http'; import * as url from 'url'; import * as fs from 'fs'; -import * as net from 'net'; import * as path from 'path'; interface Deferred { @@ -14,58 +13,17 @@ interface Deferred { reject: (reason: any) => void; } -const _typeof = { - number: 'number', - string: 'string', - undefined: 'undefined', - object: 'object', - function: 'function' -}; - -/** - * @returns whether the provided parameter is undefined. - */ -export function isUndefined(obj: any): obj is undefined { - return typeof (obj) === _typeof.undefined; -} - -/** - * @returns whether the provided parameter is undefined or null. - */ -export function isUndefinedOrNull(obj: any): obj is undefined | null { - return isUndefined(obj) || obj === null; -} - /** * Asserts that the argument passed in is neither undefined nor null. */ -export function assertIsDefined(arg: T | null | undefined): T { - if (isUndefinedOrNull(arg)) { +function assertIsDefined(arg: T | null | undefined): T { + if (typeof (arg) === 'undefined' || arg === null) { throw new Error('Assertion Failed: argument is undefined or null'); } return arg; } -export function createTerminateServer(server: http.Server) { - const sockets: Record = {}; - let socketCount = 0; - server.on('connection', socket => { - const id = socketCount++; - sockets[id] = socket; - socket.on('close', () => { - delete sockets[id]; - }); - }); - return async () => { - const result = new Promise(resolve => server.close(resolve)); - for (const id in sockets) { - sockets[id].destroy(); - } - return result; - }; -} - export async function startServer(server: http.Server): Promise { let portTimer: NodeJS.Timer; diff --git a/extensions/microsoft-authentication/src/env/browser/authServer.ts b/extensions/microsoft-authentication/src/env/browser/authServer.ts new file mode 100644 index 0000000000..cc65280df9 --- /dev/null +++ b/extensions/microsoft-authentication/src/env/browser/authServer.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function startServer(_: any): any { + throw new Error('Not implemented'); +} + +export function createServer(_: any): any { + throw new Error('Not implemented'); +} diff --git a/extensions/microsoft-authentication/src/env/browser/sha256.ts b/extensions/microsoft-authentication/src/env/browser/sha256.ts new file mode 100644 index 0000000000..a7014de2a5 --- /dev/null +++ b/extensions/microsoft-authentication/src/env/browser/sha256.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export async function sha256(s: string | Uint8Array): Promise { + const createHash = require('sha.js'); + return createHash('sha256').update(s).digest('base64'); +} diff --git a/extensions/microsoft-authentication/src/env/node/sha256.ts b/extensions/microsoft-authentication/src/env/node/sha256.ts new file mode 100644 index 0000000000..de76241ad0 --- /dev/null +++ b/extensions/microsoft-authentication/src/env/node/sha256.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export async function sha256(s: string | Uint8Array): Promise { + return (require('crypto')).createHash('sha256').update(s).digest('base64'); +} diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index 1225709307..86c288d3c6 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -1,13 +1,21 @@ { - "extends": "../shared.tsconfig.json", "compilerOptions": { - "outDir": "./out", + "baseUrl": ".", "experimentalDecorators": true, - "typeRoots": [ - "./node_modules/@types" - ] + "forceConsistentCasingInFileNames": true, + "lib": ["es2019"], + "module": "commonjs", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": false, + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": "src", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "es2019" }, - "include": [ - "src/**/*" - ] + "exclude": ["node_modules"] } diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index a09801af2c..df970ce40d 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -2,22 +2,6 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== - dependencies: - "@babel/highlight" "^7.8.3" - -"@babel/highlight@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" - integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^4.0.0" - "@types/keytar@^4.0.1": version "4.4.2" resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32" @@ -25,15 +9,42 @@ dependencies: keytar "*" +"@types/node-fetch@^2.5.7": + version "2.5.7" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" + integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + +"@types/node@*": + version "14.0.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806" + integrity sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw== + "@types/node@^10.12.21": version "10.17.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== -"@types/uuid@^3.4.6": - version "3.4.8" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.8.tgz#4ba887fcef88bd9a7515ca2de336d691e3e18318" - integrity sha512-zHWce3allXWSmRx6/AGXKCtSOA7JjeWd2L3t4aHfysNk8mouQnWCocveaT7a4IEIlPVHp81jzlnknqTgCjCLXA== +"@types/randombytes@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/randombytes/-/randombytes-2.0.0.tgz#0087ff5e60ae68023b9bc4398b406fea7ad18304" + integrity sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA== + dependencies: + "@types/node" "*" + +"@types/sha.js@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/sha.js/-/sha.js-2.4.0.tgz#bce682ef860b40f419d024fa08600c3b8d24bb01" + integrity sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ== + dependencies: + "@types/node" "*" + +"@types/uuid@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" + integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== ansi-regex@^2.0.0: version "2.1.1" @@ -45,13 +56,6 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -74,17 +78,15 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== bl@^3.0.0: version "3.0.0" @@ -93,27 +95,13 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== +buffer@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - -chalk@^2.0.0, chalk@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" + base64-js "^1.0.2" + ieee754 "^1.1.4" chownr@^1.1.1: version "1.1.3" @@ -125,27 +113,12 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -commander@^2.12.1: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + delayed-stream "~1.0.0" console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" @@ -169,6 +142,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -191,10 +169,10 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.0" -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +emitter-component@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6" + integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY= end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" @@ -203,36 +181,25 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +form-data@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" + integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -252,37 +219,17 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob@^7.1.1: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -inherits@2, inherits@^2.0.3, inherits@~2.0.3: +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -309,19 +256,6 @@ isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - keytar@*: version "5.0.0" resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.0.0.tgz#c89b6b7a4608fd7af633d9f8474b1a7eb97cbe6f" @@ -330,18 +264,23 @@ keytar@*: nan "2.14.0" prebuild-install "5.3.3" +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.12: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + mimic-response@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -376,6 +315,11 @@ node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + noop-logger@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" @@ -401,23 +345,13 @@ object-assign@^4.1.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - prebuild-install@5.3.3: version "5.3.3" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" @@ -452,6 +386,12 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +"randombytes@github:rmacfarlane/randombytes#b28d4ecee46262801ea09f15fa1f1513a05c5971": + version "2.1.0" + resolved "https://codeload.github.com/rmacfarlane/randombytes/tar.gz/b28d4ecee46262801ea09f15fa1f1513a05c5971" + dependencies: + safe-buffer "^5.1.0" + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -484,18 +424,16 @@ readable-stream@^3.0.1, readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -resolve@^1.3.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" - integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ== - dependencies: - path-parse "^1.0.6" - safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -511,6 +449,14 @@ set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +sha.js@2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -530,10 +476,12 @@ simple-get@^3.0.3: once "^1.3.1" simple-concat "^1.0.0" -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +stream@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef" + integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8= + dependencies: + emitter-component "^1.1.1" string-width@^1.0.1: version "1.0.2" @@ -585,13 +533,6 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - tar-fs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" @@ -613,37 +554,6 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tslib@^1.8.0, tslib@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - -tslint@^5.12.1: - version "5.20.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" - integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -661,10 +571,10 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@^3.3.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" + integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== vscode-extension-telemetry@0.1.1: version "0.1.1" diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 1d6d7a1f81..b030bd3994 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -79,7 +79,7 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) { }; return merge(defaultConfig, extConfig); -}; +} function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { @@ -136,7 +136,7 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { }; return merge(defaultConfig, extConfig); -}; +} module.exports = withNodeDefaults; diff --git a/extensions/xml/xsl.language-configuration.json b/extensions/xml/xsl.language-configuration.json index 7605fc4a7d..cf787c79ed 100644 --- a/extensions/xml/xsl.language-configuration.json +++ b/extensions/xml/xsl.language-configuration.json @@ -4,7 +4,11 @@ "blockComment": [""] }, "brackets": [ - ["<", ">"] + [""], + ["<", ">"], + ["{", "}"], + ["(", ")"], + ["[", "]"] ] // enhancedBrackets: [{ diff --git a/package.json b/package.json index 7898c31bdf..2b40064f04 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,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 1e16cde724..c1a32ce532 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 69566719f4..1396749802 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -37,8 +37,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% REM 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% REM if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index f57534c448..d1932f47e8 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -41,7 +41,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 @@ -49,6 +48,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 "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --no-cached-data --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index 85e6ed1fce..a3330b2bd7 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 eb23b2f989..dce346c78a 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|sql)\//, // {{SQL CARBON EDIT}} include sql in regex }; @@ -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 53202b5bb0..98e40004a8 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 183f9e072c..53a9d9ae77 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); @@ -38,13 +42,13 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}} 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); @@ -62,23 +66,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 @@ -117,9 +139,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']; @@ -261,9 +280,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 575a779d5d..82acc8995b 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 496875b922..88e1ac0676 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -476,6 +476,8 @@ export namespace Codicon { export const record = new Codicon('record', { character: '\\eba7' }); 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 a7a4cde64b..8ed59b372a 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 9b48f33401..91a47c48d1 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/common/uri.ts b/src/vs/base/common/uri.ts index 3a04006795..34aeec5345 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -252,7 +252,7 @@ export class URI implements UriComponents { return this; } - return new CachingURI(scheme, authority, path, query, fragment); + return new Uri(scheme, authority, path, query, fragment); } // ---- parse & validate ------------------------ @@ -266,9 +266,9 @@ export class URI implements UriComponents { static parse(value: string, _strict: boolean = false): URI { const match = _regexp.exec(value); if (!match) { - return new CachingURI(_empty, _empty, _empty, _empty, _empty); + return new Uri(_empty, _empty, _empty, _empty, _empty); } - return new CachingURI( + return new Uri( match[2] || _empty, percentDecode(match[4] || _empty), percentDecode(match[5] || _empty), @@ -323,11 +323,11 @@ export class URI implements UriComponents { } } - return new CachingURI('file', authority, path, _empty, _empty); + return new Uri('file', authority, path, _empty, _empty); } static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI { - return new CachingURI( + return new Uri( components.scheme, components.authority, components.path, @@ -388,7 +388,7 @@ export class URI implements UriComponents { } else if (data instanceof URI) { return data; } else { - const result = new CachingURI(data); + const result = new Uri(data); result._formatted = (data).external; result._fsPath = (data)._sep === _pathSepMarker ? (data).fsPath : null; return result; @@ -414,7 +414,7 @@ interface UriState extends UriComponents { const _pathSepMarker = isWindows ? 1 : undefined; // This class exists so that URI is compatibile with vscode.Uri (API). -class CachingURI extends URI { +class Uri extends URI { _formatted: string | null = null; _fsPath: string | null = null; diff --git a/src/vs/base/node/terminalEncoding.ts b/src/vs/base/node/terminalEncoding.ts index dbb1c7ada7..983ddf78f2 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 a679309536..9c6ec7ecff 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 ad7517a283..05f5e815b6 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 bb4cec959a..1e24797783 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 5449340452..e3ccd99dc5 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 Source EULA. 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 adfeed1c32..6b519e8d8b 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -12,7 +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 { mark } from 'vs/base/common/performance'; +import { localize } from 'vs/nls'; interface ICredential { service: string; @@ -278,10 +278,6 @@ class WorkspaceProvider implements IWorkspaceProvider { (function () { - // Mark start of workbench - mark('didLoadWorkbenchMain'); - performance.mark('workbench-start'); - // Find config by checking for DOM const configElement = document.getElementById('vscode-workbench-web-configuration'); const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; @@ -350,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 1a8085fcc1..6c0c89a30c 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 4af3b8d4f2..bff190cb36 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 95048b0269..6f70d6fa6c 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 64bb191abd..a94b8f7785 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 4ab39785c0..89819444c5 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 7423dd8ebf..0ebc677700 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 77643c5f7c..45499ad5c8 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/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index cf4ce1d857..d946bb2530 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -419,7 +419,7 @@ export function registerModelAndPositionCommand(id: string, handler: (model: ITe const model = accessor.get(IModelService).getModel(resource); if (model) { const editorPosition = Position.lift(position); - return handler(model, editorPosition, args.slice(2)); + return handler(model, editorPosition, ...args.slice(2)); } return accessor.get(ITextModelService).createModelReference(resource).then(reference => { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index af5fdc9e05..02f6fe3c9b 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 425e73468f..afdeb9d7ed 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2576,9 +2576,9 @@ class EditorPixelRatio extends ComputedEditorOption>; diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 6adfd31aaf..b40dad2267 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 63b4fb19c4..17eae643ab 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 aaf96540cf..dc13e304a4 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 0b588a6903..38c65c3c91 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/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index cff426fffa..aab5e208b1 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; +import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -33,6 +33,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, ConfigurationScope, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; +import { assertType } from 'vs/base/common/types'; class RenameSkeleton { @@ -350,11 +351,9 @@ registerEditorCommand(new RenameCommand({ // ---- api bridge command -registerDefaultLanguageCommand('_executeDocumentRenameProvider', function (model, position, args) { - let { newName } = args; - if (typeof newName !== 'string') { - throw illegalArgument('newName'); - } +registerModelAndPositionCommand('_executeDocumentRenameProvider', function (model, position, ...args) { + const [newName] = args; + assertType(typeof newName === 'string'); return rename(model, position, newName); }); diff --git a/src/vs/editor/test/common/config/commonEditorConfig.test.ts b/src/vs/editor/test/common/config/commonEditorConfig.test.ts index 200b05ff89..9032ca2029 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 7ae23864dc..795c652a5f 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 5af6cbb0b2..af8c38a7d6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3561,9 +3561,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/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index ea466271c7..c9f7f398e5 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; import { IStringDictionary } from 'vs/base/common/collections'; export const IConfigurationService = createDecorator('configurationService'); @@ -354,10 +354,6 @@ export function getDefaultValues(): any { return valueTreeRoot; } -export function overrideIdentifierFromKey(key: string): string { - return key.substring(1, key.length - 1); -} - export function keyFromOverrideIdentifier(overrideIdentifier: string): string { return `[${overrideIdentifier}]`; } diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 040dc2c8af..1eb7684da9 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -9,8 +9,8 @@ import * as arrays from 'vs/base/common/arrays'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; +import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; +import { IOverrides, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { Registry } from 'vs/platform/registry/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index df0fb0011e..c86a976bd7 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; import * as types from 'vs/base/common/types'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IStringDictionary } from 'vs/base/common/collections'; export const Extensions = { Configuration: 'base.contributions.configuration' @@ -35,12 +35,12 @@ export interface IConfigurationRegistry { /** * Register multiple default configurations to the registry. */ - registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void; + registerDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void; /** * Deregister multiple default configurations from the registry. */ - deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void; + deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void; /** * Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values. @@ -131,12 +131,6 @@ export interface IConfigurationNode { extensionInfo?: IConfigurationExtensionInfo; } -export interface IDefaultConfigurationExtension { - id: ExtensionIdentifier; - name: string; - defaults: { [key: string]: {} }; -} - type SettingProperties = { [key: string]: any }; export const allSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; @@ -152,7 +146,8 @@ const contributionRegistry = Registry.as(JSONExtensio class ConfigurationRegistry implements IConfigurationRegistry { - private readonly defaultOverridesConfigurationNode: IConfigurationNode; + private readonly defaultValues: IStringDictionary; + private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode; private readonly configurationContributors: IConfigurationNode[]; private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema }; private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema }; @@ -166,12 +161,13 @@ class ConfigurationRegistry implements IConfigurationRegistry { readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; constructor() { - this.defaultOverridesConfigurationNode = { + this.defaultValues = {}; + this.defaultLanguageConfigurationOverridesNode = { id: 'defaultOverrides', - title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"), + title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"), properties: {} }; - this.configurationContributors = [this.defaultOverridesConfigurationNode]; + this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode]; this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; this.excludedConfigurationProperties = {}; @@ -202,29 +198,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { if (configuration.properties) { for (const key in configuration.properties) { properties.push(key); - delete this.configurationProperties[key]; - - // Delete from schema - delete allSettings.properties[key]; - switch (configuration.properties[key].scope) { - case ConfigurationScope.APPLICATION: - delete applicationSettings.properties[key]; - break; - case ConfigurationScope.MACHINE: - delete machineSettings.properties[key]; - break; - case ConfigurationScope.MACHINE_OVERRIDABLE: - delete machineOverridableSettings.properties[key]; - break; - case ConfigurationScope.WINDOW: - delete windowSettings.properties[key]; - break; - case ConfigurationScope.RESOURCE: - case ConfigurationScope.LANGUAGE_OVERRIDABLE: - delete resourceSettings.properties[key]; - break; - } + this.removeFromSchema(key, configuration.properties[key]); } } if (configuration.allOf) { @@ -244,41 +219,60 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidUpdateConfiguration.fire(properties); } - public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void { + public registerDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void { const properties: string[] = []; + const overrideIdentifiers: string[] = []; for (const defaultConfiguration of defaultConfigurations) { - for (const key in defaultConfiguration.defaults) { - const defaultValue = defaultConfiguration.defaults[key]; - if (OVERRIDE_PROPERTY_PATTERN.test(key) && typeof defaultValue === 'object') { - const propertySchema: IConfigurationPropertySchema = { + for (const key in defaultConfiguration) { + properties.push(key); + this.defaultValues[key] = defaultConfiguration[key]; + + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + const property: IConfigurationPropertySchema = { type: 'object', - default: defaultValue, - description: nls.localize('overrideSettings.description', "Configure editor settings to be overridden for {0} language.", key), + default: this.defaultValues[key], + description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key), $ref: resourceLanguageSettingsSchemaId }; - allSettings.properties[key] = propertySchema; - this.defaultOverridesConfigurationNode.properties![key] = propertySchema; - this.configurationProperties[key] = propertySchema; - properties.push(key); + overrideIdentifiers.push(overrideIdentifierFromKey(key)); + this.configurationProperties[key] = property; + this.defaultLanguageConfigurationOverridesNode.properties![key] = property; + } else { + const property = this.configurationProperties[key]; + if (property) { + this.updatePropertyDefaultValue(key, property); + this.updateSchema(key, property); + } } } } + this.registerOverrideIdentifiers(overrideIdentifiers); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire(properties); } - public deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void { + public deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void { const properties: string[] = []; for (const defaultConfiguration of defaultConfigurations) { - for (const key in defaultConfiguration.defaults) { + for (const key in defaultConfiguration) { properties.push(key); - delete allSettings.properties[key]; - delete this.defaultOverridesConfigurationNode.properties![key]; - delete this.configurationProperties[key]; + delete this.defaultValues[key]; + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + delete this.configurationProperties[key]; + delete this.defaultLanguageConfigurationOverridesNode.properties![key]; + } else { + const property = this.configurationProperties[key]; + if (property) { + this.updatePropertyDefaultValue(key, property); + this.updateSchema(key, property); + } + } } } + + this.updateOverridePropertyPatternKey(); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire(properties); } @@ -291,7 +285,6 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const overrideIdentifier of overrideIdentifiers) { this.overrideIdentifiers.add(overrideIdentifier); } - this.updateOverridePropertyPatternKey(); } @@ -305,12 +298,13 @@ class ConfigurationRegistry implements IConfigurationRegistry { delete properties[key]; continue; } - // fill in default values - let property = properties[key]; - let defaultValue = property.default; - if (types.isUndefined(defaultValue)) { - property.default = getDefaultValue(property.type); - } + + const property = properties[key]; + + // update default value + this.updatePropertyDefaultValue(key, property); + + // update scope if (OVERRIDE_PROPERTY_PATTERN.test(key)) { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { @@ -361,28 +355,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { let properties = configuration.properties; if (properties) { for (const key in properties) { - allSettings.properties[key] = properties[key]; - switch (properties[key].scope) { - case ConfigurationScope.APPLICATION: - applicationSettings.properties[key] = properties[key]; - break; - case ConfigurationScope.MACHINE: - machineSettings.properties[key] = properties[key]; - break; - case ConfigurationScope.MACHINE_OVERRIDABLE: - machineOverridableSettings.properties[key] = properties[key]; - break; - case ConfigurationScope.WINDOW: - windowSettings.properties[key] = properties[key]; - break; - case ConfigurationScope.RESOURCE: - resourceSettings.properties[key] = properties[key]; - break; - case ConfigurationScope.LANGUAGE_OVERRIDABLE: - resourceSettings.properties[key] = properties[key]; - this.resourceLanguageSettingsSchema.properties![key] = properties[key]; - break; - } + this.updateSchema(key, properties[key]); } } let subNodes = configuration.allOf; @@ -393,6 +366,53 @@ class ConfigurationRegistry implements IConfigurationRegistry { register(configuration); } + private updateSchema(key: string, property: IConfigurationPropertySchema): void { + allSettings.properties[key] = property; + switch (property.scope) { + case ConfigurationScope.APPLICATION: + applicationSettings.properties[key] = property; + break; + case ConfigurationScope.MACHINE: + machineSettings.properties[key] = property; + break; + case ConfigurationScope.MACHINE_OVERRIDABLE: + machineOverridableSettings.properties[key] = property; + break; + case ConfigurationScope.WINDOW: + windowSettings.properties[key] = property; + break; + case ConfigurationScope.RESOURCE: + resourceSettings.properties[key] = property; + break; + case ConfigurationScope.LANGUAGE_OVERRIDABLE: + resourceSettings.properties[key] = property; + this.resourceLanguageSettingsSchema.properties![key] = property; + break; + } + } + + private removeFromSchema(key: string, property: IConfigurationPropertySchema): void { + delete allSettings.properties[key]; + switch (property.scope) { + case ConfigurationScope.APPLICATION: + delete applicationSettings.properties[key]; + break; + case ConfigurationScope.MACHINE: + delete machineSettings.properties[key]; + break; + case ConfigurationScope.MACHINE_OVERRIDABLE: + delete machineOverridableSettings.properties[key]; + break; + case ConfigurationScope.WINDOW: + delete windowSettings.properties[key]; + break; + case ConfigurationScope.RESOURCE: + case ConfigurationScope.LANGUAGE_OVERRIDABLE: + delete resourceSettings.properties[key]; + break; + } + } + private updateOverridePropertyPatternKey(): void { for (const overrideIdentifier of this.overrideIdentifiers.values()) { const overrideIdentifierProperty = `[${overrideIdentifier}]`; @@ -401,8 +421,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."), errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."), $ref: resourceLanguageSettingsSchemaId, - default: this.defaultOverridesConfigurationNode.properties![overrideIdentifierProperty]?.default }; + this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema); allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; @@ -412,11 +432,26 @@ class ConfigurationRegistry implements IConfigurationRegistry { } this._onDidSchemaChange.fire(); } + + private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void { + let defaultValue = this.defaultValues[key]; + if (types.isUndefined(defaultValue)) { + defaultValue = property.default; + } + if (types.isUndefined(defaultValue)) { + defaultValue = getDefaultValue(property.type); + } + property.default = defaultValue; + } } const OVERRIDE_PROPERTY = '\\[.*\\]$'; export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY); +export function overrideIdentifierFromKey(key: string): string { + return key.substring(1, key.length - 1); +} + export function getDefaultValue(type: string | string[] | undefined): any { const t = Array.isArray(type) ? (type)[0] : type; switch (t) { diff --git a/src/vs/platform/electron/common/electron.ts b/src/vs/platform/electron/common/electron.ts index 4b73ab3f28..7fdd75d4e0 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 75abab147f..ea197fd5c6 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 466cff6e15..ed3bcb4f40 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; // {{SQL CARBON EDIT}} Start aad?: boolean; @@ -190,6 +192,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' }, @@ -204,6 +207,7 @@ export const OPTIONS: OptionDescriptions> = { 'trace-options': { type: 'string' }, 'force-user-env': { type: 'boolean' }, 'open-devtools': { type: 'boolean' }, + '__sandbox': { type: 'boolean' }, // {{SQL CARBON EDIT}} Start 'command': { type: 'string', alias: 'c', cat: 'o', args: 'command-name', description: localize('commandParameter', 'Name of command to run') }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index b5e6e0c862..f8035dcfdb 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 6e762f37ec..f93a7d3f98 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 b7d7b0053a..979a441420 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -92,7 +92,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)); @@ -102,9 +103,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 997e97bc8d..82bac80e86 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 fd0c92f823..a92b4101d5 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 10b82a94e6..4d1bf88d9e 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 fb3ed3e100..afaf5d9c76 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 a755bffb66..5af383992c 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 b8b57eed0f..2563bb819b 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 b4b8f09fdb..4481ce018e 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview, - IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change + IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources'; @@ -57,11 +57,11 @@ export interface IMergableResourcePreview extends IBaseResourcePreview { readonly remoteContent: string | null; readonly localContent: string | null; readonly previewContent: string | null; + readonly acceptedContent: string | null; readonly hasConflicts: boolean; - merged: boolean; } -export type IResourcePreview = Omit; +export type IResourcePreview = Omit; export interface ISyncResourcePreview extends IBaseSyncResourcePreview { readonly remoteUserData: IRemoteUserData; @@ -217,6 +217,19 @@ export abstract class AbstractSynchroniser extends Disposable { return this._sync(manifest, false, headers); } + async apply(force: boolean, headers: IHeaders = {}): Promise { + try { + this.syncHeaders = { ...headers }; + + const status = await this.doApply(force); + this.setStatus(status); + + return this.syncPreviewPromise; + } finally { + this.syncHeaders = {}; + } + } + private async _sync(manifest: IUserDataManifest | null, apply: boolean, headers: IHeaders): Promise { try { this.syncHeaders = { ...headers }; @@ -350,14 +363,18 @@ export abstract class AbstractSynchroniser extends Disposable { this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, apply, token)); } - if (apply) { - const preview = await this.syncPreviewPromise; - const newConflicts = preview.resourcePreviews.filter(({ hasConflicts }) => hasConflicts); - return await this.updateConflictsAndApply(newConflicts, false); - } else { - return SyncStatus.Syncing; + const preview = await this.syncPreviewPromise; + this.updateConflicts(preview.resourcePreviews); + if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { + return SyncStatus.HasConflicts; } + if (apply) { + return await this.doApply(false); + } + + return SyncStatus.Syncing; + } catch (error) { // reset preview on error @@ -367,76 +384,91 @@ export abstract class AbstractSynchroniser extends Disposable { } } - async acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders = {}): Promise { + async accept(resource: URI, content: string | null): Promise { + await this.updateSyncResourcePreview(resource, async (resourcePreview) => { + const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content); + return { + ...updatedResourcePreview, + mergeState: MergeState.Accepted + }; + }); + return this.syncPreviewPromise; + } + + async merge(resource: URI): Promise { + await this.updateSyncResourcePreview(resource, async (resourcePreview) => { + const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent); + return { + ...updatedResourcePreview, + mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted + }; + }); + return this.syncPreviewPromise; + } + + 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); + return { + ...updatedResourcePreview, + mergeState: MergeState.Preview + }; + }); + return this.syncPreviewPromise; + } + + private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise): Promise { if (!this.syncPreviewPromise) { - return null; + return; } - try { - this.syncHeaders = { ...headers }; - const preview = await this.syncPreviewPromise; - this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token)); - return this.merge(resource, force, headers); - } finally { - this.syncHeaders = {}; + let preview = await this.syncPreviewPromise; + const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) => + isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)); + if (index === -1) { + return; + } + + this.syncPreviewPromise = createCancelablePromise(async token => { + const resourcePreviews = [...preview.resourcePreviews]; + resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]); + return { + ...preview, + resourcePreviews + }; + }); + + preview = await this.syncPreviewPromise; + this.updateConflicts(preview.resourcePreviews); + if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { + this.setStatus(SyncStatus.HasConflicts); + } else { + this.setStatus(SyncStatus.Syncing); } } - async merge(resource: URI, force: boolean, headers: IHeaders = {}): Promise { - if (!this.syncPreviewPromise) { - return null; - } - - try { - this.syncHeaders = { ...headers }; - const preview = await this.syncPreviewPromise; - const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) => - isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)); - if (!resourcePreview) { - return preview; - } - - /* mark merged */ - resourcePreview.merged = true; - - /* Add or remove the preview from conflicts */ - const newConflicts = [...this._conflicts]; - const index = newConflicts.findIndex(({ localResource, remoteResource, previewResource }) => - isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)); - if (resourcePreview.hasConflicts) { - if (newConflicts.indexOf(resourcePreview) === -1) { - newConflicts.push(resourcePreview); - } - } else { - if (index !== -1) { - newConflicts.splice(index, 1); - } - } - - const status = await this.updateConflictsAndApply(newConflicts, force); - this.setStatus(status); - return this.syncPreviewPromise; - - } finally { - this.syncHeaders = {}; - } + protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise { + return { + ...resourcePreview, + acceptedContent + }; } - private async updateConflictsAndApply(conflicts: IMergableResourcePreview[], force: boolean): Promise { + private async doApply(force: boolean): Promise { if (!this.syncPreviewPromise) { return SyncStatus.Idle; } const preview = await this.syncPreviewPromise; - // update conflicts - this.updateConflicts(conflicts); - if (this._conflicts.length) { + // check for conflicts + if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { return SyncStatus.HasConflicts; } - // check if all are merged - if (preview.resourcePreviews.some(r => !r.merged)) { + // check if all are accepted + if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) { return SyncStatus.Syncing; } @@ -452,39 +484,14 @@ export abstract class AbstractSynchroniser extends Disposable { return SyncStatus.Idle; } - private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) => - (localChange !== Change.None || remoteChange !== Change.None) - && (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource))); - if (index !== -1) { - const resourcePreviews = [...preview.resourcePreviews]; - const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token); - resourcePreviews[index] = { ...resourcePreview, merged: resourcePreviews[index].merged }; - preview = { - ...preview, - resourcePreviews - }; - } - return preview; - } - - protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - return { - ...resourcePreview, - previewContent, - hasConflicts: false, - localChange: Change.Modified, - remoteChange: Change.Modified, - }; - } - private async clearPreviewFolder(): Promise { try { await this.fileService.del(this.syncPreviewFolder, { recursive: true }); } catch (error) { /* Ignore */ } } - private updateConflicts(conflicts: IMergableResourcePreview[]): void { + private updateConflicts(previews: IMergableResourcePreview[]): void { + const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict); if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) { this._conflicts = conflicts; this._onDidChangeConflicts.fire(conflicts); @@ -542,14 +549,14 @@ export abstract class AbstractSynchroniser extends Disposable { const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null; if (syncPreview) { for (const resourcePreview of syncPreview.resourcePreviews) { - if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) { - return resourcePreview.previewContent || ''; + if (isEqual(resourcePreview.acceptedResource, uri)) { + return resourcePreview.acceptedContent; } - if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) { - return resourcePreview.remoteContent || ''; + if (isEqual(resourcePreview.remoteResource, uri)) { + return resourcePreview.remoteContent; } - if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) { - return resourcePreview.localContent || ''; + if (isEqual(resourcePreview.localResource, uri)) { + return resourcePreview.localContent; } } } @@ -562,21 +569,31 @@ export abstract class AbstractSynchroniser extends Disposable { } catch (e) { /* ignore */ } } - private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, merge: boolean, token: CancellationToken): Promise { + private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, token: CancellationToken): Promise { const machineId = await this.currentMachineIdPromise; const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId; // For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData; - const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); + const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); - // Mark merge - const mergableResourcePreviews = resourcePreviews.map(r => ({ - ...r, - merged: merge || (r.localChange === Change.None && r.remoteChange === Change.None) /* Mark previews with no changes as merged */ - })); + const resourcePreviews: IMergableResourcePreview[] = []; + for (const resourcePreview of result) { + if (token.isCancellationRequested) { + break; + } + if (!apply) { + await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || '')); + } + resourcePreviews.push({ + ...resourcePreview, + mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */ + : apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted) + : MergeState.Preview + }); + } - return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine }; + return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine }; } async getLastSyncUserData(): Promise { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 64f83c4483..7d7d8ea081 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -46,8 +46,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse */ protected readonly version: number = 3; protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } - private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json'); - private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json'); + private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); + private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -94,12 +96,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const mergeResult = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions); const { added, removed, updated } = mergeResult; return [{ - localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI, + localResource: this.localResource, localContent: this.format(localExtensions), - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent: null, + acceptedResource: this.acceptedResource, + acceptedContent: null, added, removed, updated, @@ -131,12 +135,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const { added, removed, updated, remote } = mergeResult; return [{ - localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI, + localResource: this.localResource, localContent: this.format(localExtensions), - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent: null, + acceptedResource: this.acceptedResource, + acceptedContent: null, added, removed, updated, @@ -177,14 +183,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } } - protected async updateResourcePreviewContent(resourcePreview: IExtensionResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - if (isEqual(resource, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { + 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); } return { ...resourcePreview, - previewContent, + acceptedContent, hasConflicts: false, localChange: Change.Modified, remoteChange: Change.Modified, @@ -195,10 +201,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); - const localResource = ExtensionsSynchroniser.EXTENSIONS_DATA_URI; + const localResource = this.localResource; const localContent = this.format(localExtensions); - const remoteResource = this.remotePreviewResource; - const previewResource = this.localPreviewResource; + const remoteResource = this.remoteResource; + const previewResource = this.previewResource; + const acceptedResource = this.acceptedResource; const previewContent = null; if (remoteExtensions !== null) { const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions); @@ -210,6 +217,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: this.format(remoteExtensions), previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, added, removed, updated, @@ -228,6 +237,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: null, previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [], localChange: Change.None, remoteChange: Change.None, @@ -243,12 +254,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const mergeResult = merge(localExtensions, null, null, [], ignoredExtensions); const { added, removed, updated, remote } = mergeResult; return { - localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI, + localResource: this.localResource, localContent: this.format(localExtensions), - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent: null, + acceptedResource: this.acceptedResource, + acceptedContent: null, added, removed, updated, @@ -266,13 +279,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } async resolveContent(uri: URI): Promise { - if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { - const installedExtensions = await this.extensionManagementService.getInstalled(); - const localExtensions = this.getLocalExtensions(installedExtensions); - return this.format(localExtensions); - } - - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) { return this.resolvePreviewContent(uri); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 6c2997f754..f63a7a7c7f 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -45,8 +45,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` }); protected readonly version: number = 1; - private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json'); - private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json'); + private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); + private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IFileService fileService: IFileService, @@ -93,12 +95,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const mergeResult = merge(localUserData.storage, syncGlobalState.storage, localUserData.storage, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); const { local, skipped } = mergeResult; return [{ - localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, + localResource: this.localResource, localContent: this.format(localUserData), - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent: null, + acceptedResource: this.acceptedResource, + acceptedContent: null, local, remote: syncGlobalState.storage, localUserData, @@ -125,12 +129,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const { local, remote, skipped } = mergeResult; return [{ - localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, + localResource: this.localResource, localContent: this.format(localGloablState), - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent: null, + acceptedResource: this.acceptedResource, + acceptedContent: null, local, remote, localUserData: localGloablState, @@ -172,11 +178,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - protected async updateResourcePreviewContent(resourcePreview: IGlobalStateResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - if (GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, resource) { + protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise { + if (isEqual(this.localResource, resource)) { return this.getPushPreview(resourcePreview.remoteContent); } - if (this.remotePreviewResource, resource) { + if (isEqual(this.remoteResource, resource)) { return this.getPullPreview(resourcePreview.remoteContent, resourcePreview.skippedStorageKeys); } return resourcePreview; @@ -184,10 +190,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private async getPullPreview(remoteContent: string | null, skippedStorageKeys: string[]): Promise { const localGlobalState = await this.getLocalGlobalState(); - const localResource = GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI; + const localResource = this.localResource; const localContent = this.format(localGlobalState); - const remoteResource = this.remotePreviewResource; - const previewResource = this.localPreviewResource; + const remoteResource = this.remoteResource; + const previewResource = this.previewResource; + const acceptedResource = this.acceptedResource; const previewContent = null; if (remoteContent !== null) { const remoteGlobalState: IGlobalState = JSON.parse(remoteContent); @@ -200,6 +207,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: this.format(remoteGlobalState), previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, local, remote, localUserData: localGlobalState, @@ -216,6 +225,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: null, previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, local: { added: {}, removed: [], updated: {} }, remote: null, localUserData: localGlobalState, @@ -231,12 +242,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const localUserData = await this.getLocalGlobalState(); const remoteGlobalState: IGlobalState = remoteContent ? JSON.parse(remoteContent) : null; return { - localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, + localResource: this.localResource, localContent: this.format(localUserData), - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent: null, + acceptedResource: this.acceptedResource, + acceptedContent: null, local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, localUserData, @@ -252,12 +265,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } async resolveContent(uri: URI): Promise { - if (isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) { - const localGlobalState = await this.getLocalGlobalState(); - return this.format(localGlobalState); - } - - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) { return this.resolvePreviewContent(uri); } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 5db62ff759..faabd29a18 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -35,8 +35,10 @@ interface ISyncContent { export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { protected readonly version: number = 1; - protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json'); - protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json'); + private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); + private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @@ -58,13 +60,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem const previewContent = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: previewContent, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: Change.None, hasConflicts: false, @@ -76,13 +80,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem const previewContent: string | null = fileContent ? fileContent.value.toString() : null; return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, localChange: Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -94,13 +100,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem const previewContent = this.getKeybindingsContentFromSyncContent(syncData.content); return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -150,17 +158,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } if (previewContent && !token.isCancellationRequested) { - await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(previewContent)); + await this.fileService.writeFile(this.previewResource, VSBuffer.fromString(previewContent)); } return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, hasConflicts, localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, @@ -168,7 +178,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0]; + let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; if (content !== null) { if (this.hasErrors(content)) { @@ -193,7 +203,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // Delete the preview try { - await this.fileService.del(this.localPreviewResource); + await this.fileService.del(this.previewResource); } catch (e) { /* ignore */ } } else { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); @@ -230,11 +240,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } async resolveContent(uri: URI): Promise { - if (isEqual(this.file, uri)) { - const fileContent = await this.getLocalFileContent(); - return fileContent ? fileContent.value.toString() : ''; - } - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index d15c065871..9562f75b3f 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -39,8 +39,10 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { protected readonly version: number = 1; - protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json'); - protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json'); + private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); + private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IFileService fileService: IFileService, @@ -66,19 +68,21 @@ 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); } return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: Change.None, hasConflicts: false, @@ -92,20 +96,22 @@ 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 [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, localChange: Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -126,13 +132,15 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -146,6 +154,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null; const ignoredSettings = await this.getIgnoredSettings(); + let acceptedContent: string | null = null; let previewContent: string | null = null; let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; @@ -156,7 +165,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.validateContent(localContent); this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions); - previewContent = result.localContent || result.remoteContent; + acceptedContent = result.localContent || result.remoteContent; hasLocalChanged = result.localContent !== null; hasRemoteChanged = result.remoteContent !== null; hasConflicts = result.hasConflicts; @@ -165,40 +174,43 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // First time syncing to remote else if (fileContent) { this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`); - previewContent = fileContent.value.toString(); + acceptedContent = fileContent.value.toString(); hasRemoteChanged = true; } - if (previewContent && !token.isCancellationRequested) { + if (acceptedContent && !token.isCancellationRequested) { // Remove the ignored settings from the preview. - const content = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formattingOptions); - await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content)); + previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions); } return [{ - localResource: this.file, + localResource: this.localResource, fileContent, localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remotePreviewResource, + remoteResource: this.remoteResource, remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.localPreviewResource, + previewResource: this.previewResource, previewContent, + acceptedResource: this.acceptedResource, + acceptedContent, localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, hasConflicts, }]; } - protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - const formatUtils = await this.getFormattingOptions(); - // Add ignored settings from local file content - const ignoredSettings = await this.getIgnoredSettings(); - previewContent = updateIgnoredSettings(previewContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); - return super.updateResourcePreviewContent(resourcePreview, resource, previewContent, token) as Promise; + 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(); + acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); + } + return super.updateResourcePreview(resourcePreview, resource, acceptedContent) as Promise; } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0]; + let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; if (content !== null) { @@ -225,7 +237,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // Delete the preview try { - await this.fileService.del(this.localPreviewResource); + await this.fileService.del(this.previewResource); } catch (e) { /* ignore */ } } else { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); @@ -260,11 +272,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } async resolveContent(uri: URI): Promise { - if (isEqual(this.file, uri)) { - const fileContent = await this.getLocalFileContent(); - return fileContent ? fileContent.value.toString() : ''; - } - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); @@ -289,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 bb6e6d33b7..9f33636c9a 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -5,7 +5,7 @@ import { IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, - USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, UserDataSyncError, UserDataSyncErrorCode, Change + USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, Change } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; @@ -20,7 +20,6 @@ import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/sn import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { deepClone } from 'vs/base/common/objects'; -import { localize } from 'vs/nls'; export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { @@ -94,34 +93,93 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets); - const resourcePreviews = this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); - - for (const resourcePreview of resourcePreviews) { - if (resourcePreview.hasConflicts) { - if (!token.isCancellationRequested) { - await this.fileService.writeFile(resourcePreview.previewResource!, VSBuffer.fromString(resourcePreview.previewContent || '')); - } - } - } - - return resourcePreviews; + return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); } - protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { + protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { return { ...resourcePreview, - previewContent: previewContent || null, - hasConflicts: false, - localChange: previewContent ? Change.Modified : Change.Deleted, - remoteChange: previewContent ? Change.Modified : Change.Deleted, + acceptedContent, + localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent), + remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent), }; } - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) { - throw new UserDataSyncError(localize('unresolved conflicts', "Error while syncing {0}. Please resolve conflicts first.", this.syncResourceLogLabel), UserDataSyncErrorCode.UnresolvedConflicts, this.resource); + private computeLocalChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change { + const isRemoteResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })); + const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder); + + const previewExists = acceptedContent !== null; + const remoteExists = resourcePreview.remoteContent !== null; + const localExists = resourcePreview.fileContent !== null; + + if (isRemoteResourceAccepted) { + if (remoteExists && localExists) { + return Change.Modified; + } + if (remoteExists && !localExists) { + return Change.Added; + } + if (!remoteExists && localExists) { + return Change.Deleted; + } + return Change.None; } + if (isPreviewResourceAccepted) { + if (previewExists && localExists) { + return Change.Modified; + } + if (previewExists && !localExists) { + return Change.Added; + } + if (!previewExists && localExists) { + return Change.Deleted; + } + return Change.None; + } + + return Change.None; + } + + private computeRemoteChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change { + const isLocalResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' })); + const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder); + + const previewExists = acceptedContent !== null; + const remoteExists = resourcePreview.remoteContent !== null; + const localExists = resourcePreview.fileContent !== null; + + if (isLocalResourceAccepted) { + if (remoteExists && localExists) { + return Change.Modified; + } + if (remoteExists && !localExists) { + return Change.Deleted; + } + if (!remoteExists && localExists) { + return Change.Added; + } + return Change.None; + } + + if (isPreviewResourceAccepted) { + if (previewExists && remoteExists) { + return Change.Modified; + } + if (previewExists && !remoteExists) { + return Change.Added; + } + if (!previewExists && remoteExists) { + return Change.Deleted; + } + return Change.None; + } + + return Change.None; + } + + protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`); } @@ -158,13 +216,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets added remotely -> add locally */ for (const key of Object.keys(mergeResult.local.added)) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: null, localContent: null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.local.added[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.local.added[key], hasConflicts: false, localChange: Change.Added, remoteChange: Change.None @@ -174,13 +234,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets updated remotely -> update locally */ for (const key of Object.keys(mergeResult.local.updated)) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.local.updated[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.local.updated[key], hasConflicts: false, localChange: Change.Modified, remoteChange: Change.None @@ -190,13 +252,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets removed remotely -> remove locally */ for (const key of mergeResult.local.removed) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: null, hasConflicts: false, localChange: Change.Deleted, remoteChange: Change.None @@ -206,13 +270,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets added locally -> add remotely */ for (const key of Object.keys(mergeResult.remote.added)) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.remote.added[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.remote.added[key], hasConflicts: false, localChange: Change.None, remoteChange: Change.Added @@ -222,13 +288,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets updated locally -> update remotely */ for (const key of Object.keys(mergeResult.remote.updated)) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.remote.updated[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.remote.updated[key], hasConflicts: false, localChange: Change.None, remoteChange: Change.Modified @@ -238,13 +306,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets removed locally -> remove remotely */ for (const key of mergeResult.remote.removed) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: null, localContent: null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: null, hasConflicts: false, localChange: Change.None, remoteChange: Change.Deleted @@ -254,13 +324,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD /* Snippets with conflicts */ for (const key of mergeResult.conflicts) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key] || null, localContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key] || null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null, hasConflicts: true, localChange: localFileContent[key] ? Change.Modified : Change.Added, remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added @@ -271,13 +343,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD for (const key of Object.keys(localFileContent)) { if (!resourcePreviews.has(key)) { resourcePreviews.set(key, { - localResource: joinPath(this.snippetsFolder, key), + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key] || null, localContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key] || null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null, hasConflicts: false, localChange: Change.None, remoteChange: Change.None @@ -308,17 +382,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } async resolveContent(uri: URI): Promise { - if (isEqualOrParent(uri, this.snippetsFolder)) { - try { - const content = await this.fileService.readFile(uri); - return content ? content.value.toString() : null; - } catch (error) { - return ''; - } - } - - if (isEqualOrParent(uri.with({ scheme: this.syncPreviewFolder.scheme }), this.syncPreviewFolder) - || isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME }))) { + if (isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })) + || isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' })) + || isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) { return this.resolvePreviewContent(uri); } @@ -362,12 +428,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) { - // Do not update if there are conflicts - return; - } - - for (const { fileContent, previewContent: content, localResource, remoteResource, localChange } of resourcePreviews) { + for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) { if (localChange !== Change.None) { const key = remoteResource ? basename(remoteResource) : basename(localResource!); const resource = joinPath(this.snippetsFolder, key); @@ -397,15 +458,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise { - if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) { - // Do not update if there are conflicts - return remoteUserData; - } - const currentSnippets: IStringDictionary = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {}; const newSnippets: IStringDictionary = deepClone(currentSnippets); - for (const { previewContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) { + for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) { if (remoteChange !== Change.None) { const key = localResource ? basename(localResource) : basename(remoteResource!); if (remoteChange === Change.Deleted) { diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 26e4335769..fc4607e39a 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 17e4075c8c..309d4ac27e 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -122,16 +122,19 @@ export function isAuthenticationProvider(thing: any): thing is IAuthenticationPr } export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined { - const value = configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY) || productService[CONFIGURATION_SYNC_STORE_KEY]; + const value = { + ...(productService[CONFIGURATION_SYNC_STORE_KEY] || {}), + ...(configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY) || {}) + }; if (value - && isString(value.url) - && isObject(value.authenticationProviders) - && Object.keys(value.authenticationProviders).every(authenticationProviderId => isArray(value.authenticationProviders[authenticationProviderId].scopes)) + && isString((value as any).url) // {{SQL CARBON EDIT}} strict-nulls + && isObject((value as any).authenticationProviders) // {{SQL CARBON EDIT}} strict-nulls + && Object.keys((value as any).authenticationProviders).every(authenticationProviderId => isArray((value as any).authenticationProviders[authenticationProviderId].scopes)) // {{SQL CARBON EDIT}} strict-nulls ) { return { - url: joinPath(URI.parse(value.url), 'v1'), - authenticationProviders: Object.keys(value.authenticationProviders).reduce((result, id) => { - result.push({ id, scopes: value.authenticationProviders[id].scopes }); + url: joinPath(URI.parse((value as any).url), 'v1'), // {{SQL CARBON EDIT}} strict-nulls + authenticationProviders: Object.keys((value as any).authenticationProviders).reduce((result, id) => { // {{SQL CARBON EDIT}} strict-nulls + result.push({ id, scopes: (value as any).authenticationProviders[id].scopes }); // {{SQL CARBON EDIT}} strict-nulls return result; }, []) }; @@ -164,6 +167,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 +213,7 @@ export enum UserDataSyncErrorCode { UpgradeRequired = 'UpgradeRequired', /* 426 */ PreconditionRequired = 'PreconditionRequired', /* 428 */ TooManyRequests = 'RemoteTooManyRequests', /* 429 */ + TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */ // Local Errors ConnectionRefused = 'ConnectionRefused', @@ -317,13 +324,20 @@ export const enum Change { Deleted, } +export const enum MergeState { + Preview = 'preview', + Conflict = 'conflict', + Accepted = 'accepted', +} + export interface IResourcePreview { readonly remoteResource: URI; readonly localResource: URI; readonly previewResource: URI; + readonly acceptedResource: URI; readonly localChange: Change; readonly remoteChange: Change; - readonly merged: boolean; + readonly mergeState: MergeState; } export interface ISyncResourcePreview { @@ -345,18 +359,20 @@ export interface IUserDataSynchroniser { pull(): Promise; push(): Promise; sync(manifest: IUserDataManifest | null, headers: IHeaders): Promise; - preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise; replace(uri: URI): Promise; stop(): Promise; + preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise; + accept(resource: URI, content: string | null): Promise; + merge(resource: URI): Promise; + discard(resource: URI): Promise; + apply(force: boolean, headers: IHeaders): Promise; + hasPreviouslySynced(): Promise; hasLocalData(): Promise; resetLocal(): Promise; resolveContent(resource: URI): Promise; - acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders): Promise; - merge(resource: URI, force: boolean, headers: IHeaders): Promise; - getRemoteSyncResourceHandles(): Promise; getLocalSyncResourceHandles(): Promise; getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>; @@ -387,8 +403,10 @@ export interface IManualSyncTask extends IDisposable { readonly manifest: IUserDataManifest | null; readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>; preview(): Promise<[SyncResource, ISyncResourcePreview][]>; - accept(uri: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]>; - merge(uri?: URI): 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][]>; pull(): Promise; push(): Promise; stop(): Promise; @@ -422,7 +440,7 @@ export interface IUserDataSyncService { hasLocalData(): Promise; hasPreviouslySynced(): Promise; resolveContent(resource: URI): Promise; - acceptPreviewContent(resource: SyncResource, conflictResource: URI, content: string): 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 f982dcaa29..b925189458 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -53,7 +53,7 @@ export class UserDataSyncChannel implements IServerChannel { case 'resetLocal': return this.service.resetLocal(); case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); case 'hasLocalData': return this.service.hasLocalData(); - case 'acceptPreviewContent': return this.service.acceptPreviewContent(args[0], URI.revive(args[1]), args[2]); + case 'accept': return this.service.accept(args[0], URI.revive(args[1]), args[2], args[3]); case 'resolveContent': return this.service.resolveContent(URI.revive(args[0])); case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]); case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]); @@ -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,10 +86,22 @@ 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]); case 'merge': return this.manualSyncTask.merge(URI.revive(args[0])); + case 'discard': return this.manualSyncTask.discard(URI.revive(args[0])); + case 'apply': return this.manualSyncTask.apply(); case 'pull': return this.manualSyncTask.pull(); case 'push': return this.manualSyncTask.push(); case 'stop': return this.manualSyncTask.stop(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 261d0f3fc5..405ddf6900 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,10 +256,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string, executionId: string = generateUuid()): Promise { + async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise { await this.checkEnablement(); const synchroniser = this.getSynchroniser(syncResource); - await synchroniser.acceptPreviewContent(resource, content, false, createSyncHeaders(executionId)); + await synchroniser.accept(resource, content); + if (apply) { + await synchroniser.apply(false, createSyncHeaders(generateUuid())); + } } async resolveContent(resource: URI): Promise { @@ -399,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: @@ -460,21 +456,21 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { return this.previews; } - async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> { - return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.acceptPreviewContent(resource, content, force, this.syncHeaders)); + async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { + return this.performAction(resource, sychronizer => sychronizer.accept(resource, content)); } - async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> { - if (resource) { - return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.merge(resource, force, this.syncHeaders)); - } else { - return this.mergeAll(); - } + async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> { + return this.performAction(resource, sychronizer => sychronizer.merge(resource)); } - private async mergeOrAccept(resource: URI, mergeOrAccept: (synchroniser: IUserDataSynchroniser, force: boolean) => Promise): Promise<[SyncResource, ISyncResourcePreview][]> { + async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> { + return this.performAction(resource, sychronizer => sychronizer.discard(resource)); + } + + private async performAction(resource: URI, action: (synchroniser: IUserDataSynchroniser) => Promise): Promise<[SyncResource, ISyncResourcePreview][]> { if (!this.previews) { - throw new Error('You need to create preview before merging or accepting'); + throw new Error('Missing preview. Create preview and try again.'); } const index = this.previews.findIndex(([, preview]) => preview.resourcePreviews.some(({ localResource, previewResource, remoteResource }) => @@ -500,9 +496,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { } const synchroniser = this.synchronisers.find(s => s.resource === this.previews![index][0])!; - /* force only if the resource is local or remote resource */ - const force = isEqual(resource, resourcePreview.localResource) || isEqual(resource, resourcePreview.remoteResource); - const preview = await mergeOrAccept(synchroniser, force); + const preview = await action(synchroniser); preview ? this.previews.splice(index, 1, this.toSyncResourcePreview(synchroniser.resource, preview)) : this.previews.splice(index, 1); const i = this.synchronizingResources.findIndex(s => s[0] === syncResource); @@ -515,25 +509,33 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { return this.previews; } - private async mergeAll(): Promise<[SyncResource, ISyncResourcePreview][]> { + async apply(): Promise<[SyncResource, ISyncResourcePreview][]> { if (!this.previews) { - throw new Error('You need to create preview before merging'); + throw new Error('You need to create preview before applying'); } if (this.synchronizingResources.length) { - throw new Error('Cannot merge while synchronizing resources'); + throw new Error('Cannot pull while synchronizing resources'); } const previews: [SyncResource, ISyncResourcePreview][] = []; 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)!; - let syncResourcePreview = null; + + /* merge those which are not yet merged */ for (const resourcePreview of preview.resourcePreviews) { - syncResourcePreview = await synchroniser.merge(resourcePreview.remoteResource, false, this.syncHeaders); + if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) { + await synchroniser.merge(resourcePreview.previewResource); + } } - if (syncResourcePreview) { - previews.push([syncResource, syncResourcePreview]); + + /* 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); } @@ -553,9 +555,10 @@ 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) || ''; - await synchroniser.acceptPreviewContent(resourcePreview.remoteResource, content, true, this.syncHeaders); + const content = await synchroniser.resolveContent(resourcePreview.remoteResource); + await synchroniser.accept(resourcePreview.remoteResource, content); } + await synchroniser.apply(true, this.syncHeaders); this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); this._onSynchronizeResources.fire(this.synchronizingResources); } @@ -574,9 +577,10 @@ 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) || ''; - await synchroniser.acceptPreviewContent(resourcePreview.localResource, content, true, this.syncHeaders); + const content = await synchroniser.resolveContent(resourcePreview.localResource); + await synchroniser.accept(resourcePreview.localResource, content); } + await synchroniser.apply(true, this.syncHeaders); this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); this._onSynchronizeResources.fire(this.synchronizingResources); } @@ -641,8 +645,9 @@ function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePr localResource: resourcePreview.localResource, previewResource: resourcePreview.previewResource, remoteResource: resourcePreview.remoteResource, + acceptedResource: resourcePreview.acceptedResource, localChange: resourcePreview.localChange, remoteChange: resourcePreview.remoteChange, - merged: resourcePreview.merged, + mergeState: resourcePreview.mergeState, }; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 4976239834..bf2c4653ec 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 70c1526b2b..ef47a4547b 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 9f915117b9..c7e745ad7c 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -287,7 +287,8 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); const conflicts = testObject.conflicts; - await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet1, false); + await testObject.accept(conflicts[0].previewResource, htmlSnippet1); + await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -327,7 +328,7 @@ suite('SnippetsSync', () => { await testObject.sync(await testClient.manifest()); let conflicts = testObject.conflicts; - await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false); + await testObject.accept(conflicts[0].previewResource, htmlSnippet2); conflicts = testObject.conflicts; assert.equal(testObject.status, SyncStatus.HasConflicts); @@ -346,8 +347,9 @@ suite('SnippetsSync', () => { await testObject.sync(await testClient.manifest()); const conflicts = testObject.conflicts; - await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false); - await testObject.acceptPreviewContent(conflicts[1].previewResource, tsSnippet1, false); + await testObject.accept(conflicts[0].previewResource, htmlSnippet2); + await testObject.accept(conflicts[1].previewResource, tsSnippet1); + await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -461,7 +463,8 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet3, testClient); await testObject.sync(await testClient.manifest()); - await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, htmlSnippet2, false); + await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet2); + await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -565,7 +568,8 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); - await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, htmlSnippet3, false); + await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet3); + await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -592,7 +596,8 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await testObject.sync(await testClient.manifest()); - await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, '', false); + await testObject.accept(testObject.conflicts[0].previewResource, null); + await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.deepEqual(testObject.conflicts, []); @@ -689,7 +694,8 @@ suite('SnippetsSync', () => { await testObject.sync(await testClient.manifest()); let conflicts = testObject.conflicts; - await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false); + await testObject.accept(conflicts[0].previewResource, htmlSnippet2); + await testObject.apply(false); const fileService = testClient.instantiationService.get(IFileService); assert.ok(!await fileService.exists(dirname(conflicts[0].previewResource))); @@ -710,7 +716,7 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false); + preview = await testObject.merge(preview!.resourcePreviews[0].localResource); assert.equal(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, @@ -736,8 +742,36 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false); - preview = await testObject.merge(preview!.resourcePreviews[1].localResource, false); + preview = await testObject.merge(preview!.resourcePreviews[0].localResource); + preview = await testObject.merge(preview!.resourcePreviews[1].localResource); + + assert.equal(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + ]); + assert.deepEqual(testObject.conflicts, []); + }); + + test('merge when there are multiple snippets and all snippets are merged and applied', async () => { + const environmentService = testClient.instantiationService.get(IEnvironmentService); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await updateSnippet('typescript.json', tsSnippet2, testClient); + let preview = await testObject.preview(await testClient.manifest()); + + assert.equal(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + ]); + assert.deepEqual(testObject.conflicts, []); + + preview = await testObject.merge(preview!.resourcePreviews[0].localResource); + preview = await testObject.merge(preview!.resourcePreviews[1].localResource); + preview = await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.equal(preview, null); @@ -762,7 +796,37 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false); + preview = await testObject.merge(preview!.resourcePreviews[0].localResource); + + assert.equal(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + ]); + assert.deepEqual(testObject.conflicts, []); + }); + + test('merge when there are multiple snippets and one snippet has no changes and one snippet is merged and applied', async () => { + const environmentService = testClient.instantiationService.get(IEnvironmentService); + + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet1, testClient); + await updateSnippet('typescript.json', tsSnippet2, testClient); + let preview = await testObject.preview(await testClient.manifest()); + + assert.equal(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + ]); + assert.deepEqual(testObject.conflicts, []); + + preview = await testObject.merge(preview!.resourcePreviews[0].localResource); + preview = await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.equal(preview, null); @@ -788,7 +852,7 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); assert.equal(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, @@ -821,8 +885,8 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false); - preview = await testObject.merge(preview!.resourcePreviews[1].previewResource, false); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + preview = await testObject.merge(preview!.resourcePreviews[1].previewResource); assert.equal(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, @@ -856,7 +920,7 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, htmlSnippet2, false); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); assert.equal(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, @@ -886,8 +950,40 @@ suite('SnippetsSync', () => { ]); assert.deepEqual(testObject.conflicts, []); - preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, htmlSnippet2, false); - preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[1].previewResource, tsSnippet2, false); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); + preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2); + + assert.equal(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + ]); + assert.deepEqual(testObject.conflicts, []); + }); + + test('accept when there are multiple snippets with conflicts and all snippets are accepted and applied', async () => { + const environmentService = testClient.instantiationService.get(IEnvironmentService); + + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await updateSnippet('typescript.json', tsSnippet2, testClient); + let preview = await testObject.preview(await testClient.manifest()); + + assert.equal(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + ]); + assert.deepEqual(testObject.conflicts, []); + + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); + preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2); + preview = await testObject.apply(false); assert.equal(testObject.status, SyncStatus.Idle); assert.equal(preview, null); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index c327a663ca..93ee8ff7e7 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; @@ -12,6 +12,8 @@ import { Barrier } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; +import { IFileService } from 'vs/platform/files/common/files'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; const resource = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'testResource', path: `/current.json` }); @@ -47,27 +49,27 @@ class TestSynchroniser extends AbstractSynchroniser { } protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { if (this.syncResult.hasError) { throw new Error('failed'); } - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, preview: IResourcePreview[], forcePush: boolean): Promise { - if (preview[0]?.previewContent) { - await this.applyRef(preview[0].previewContent); + if (preview[0]?.acceptedContent) { + await this.applyRef(preview[0].acceptedContent); } } @@ -106,6 +108,7 @@ suite('TestSynchronizer', () => { await client.setUp(); userDataSyncStoreService = client.instantiationService.get(IUserDataSyncStoreService); disposableStore.add(toDisposable(() => userDataSyncStoreService.clear())); + client.instantiationService.get(IFileService).registerProvider(USER_DATA_SYNC_SCHEME, new InMemoryFileSystemProvider()); }); teardown(() => disposableStore.clear()); @@ -276,26 +279,70 @@ suite('TestSynchronizer', () => { assertConflicts(testObject.conflicts, []); }); - test('preview: status is set to idle after merging if there are no conflicts', async () => { + test('preview: status is syncing after merging if there are no conflicts', async () => { const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + + assert.deepEqual(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, [resource]); + assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); + assertConflicts(testObject.conflicts, []); + }); + + test('preview: status is set to idle after merging and applying if there are no conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { hasConflicts: false, hasError: false }; + testObject.syncBarrier.open(); + + let preview = await testObject.preview(await client.manifest()); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); assert.equal(preview, null); assertConflicts(testObject.conflicts, []); }); + test('preview: discarding the merge', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { hasConflicts: false, hasError: false }; + testObject.syncBarrier.open(); + + let preview = await testObject.preview(await client.manifest()); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); + + assert.deepEqual(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, [resource]); + assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assertConflicts(testObject.conflicts, []); + }); + + test('preview: status is syncing after accepting when there are no conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { hasConflicts: false, hasError: false }; + testObject.syncBarrier.open(); + + let preview = await testObject.preview(await client.manifest()); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + + assert.deepEqual(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, [resource]); + assertConflicts(testObject.conflicts, []); + }); + test('preview: status is set to idle and sync is applied after accepting when there are no conflicts before merging', async () => { const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); assert.equal(preview, null); @@ -319,35 +366,80 @@ suite('TestSynchronizer', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - const preview = await testObject.preview(await client.manifest()); - await testObject.merge(preview!.resourcePreviews[0].previewResource, false); + let preview = await testObject.preview(await client.manifest()); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); assert.deepEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [resource]); + assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); assertConflicts(testObject.conflicts, [preview!.resourcePreviews[0].previewResource]); }); + test('preview: discarding the conflict', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { hasConflicts: true, hasError: false }; + testObject.syncBarrier.open(); + + const preview = await testObject.preview(await client.manifest()); + await testObject.merge(preview!.resourcePreviews[0].previewResource); + await testObject.discard(preview!.resourcePreviews[0].previewResource); + + assert.deepEqual(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, [resource]); + assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview); + assertConflicts(testObject.conflicts, []); + }); + + test('preview: status is syncing after accepting when there are conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { hasConflicts: true, hasError: false }; + testObject.syncBarrier.open(); + + let preview = await testObject.preview(await client.manifest()); + await testObject.merge(preview!.resourcePreviews[0].previewResource); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + + assert.deepEqual(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, [resource]); + assert.deepEqual(testObject.conflicts, []); + }); + test('preview: status is set to idle and sync is applied after accepting when there are conflicts', async () => { const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - await testObject.merge(preview!.resourcePreviews[0].previewResource, false); - preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false); + await testObject.merge(preview!.resourcePreviews[0].previewResource); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); assert.equal(preview, null); assertConflicts(testObject.conflicts, []); }); + test('preview: status is set to syncing after accepting when there are conflicts before merging', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { hasConflicts: true, hasError: false }; + testObject.syncBarrier.open(); + + let preview = await testObject.preview(await client.manifest()); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + + assert.deepEqual(testObject.status, SyncStatus.Syncing); + assertPreviews(preview!.resourcePreviews, [resource]); + assertConflicts(testObject.conflicts, []); + }); + test('preview: status is set to idle and sync is applied after accepting when there are conflicts before merging', async () => { const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); assert.equal(preview, null); diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 7de679486e..6125b6d77e 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 beef31a5af..8fc77015da 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 1e694fca28..bf4fa9a3a8 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/platform/webview/common/resourceLoader.ts b/src/vs/platform/webview/common/resourceLoader.ts index 4a01ab32cd..d530ca0658 100644 --- a/src/vs/platform/webview/common/resourceLoader.ts +++ b/src/vs/platform/webview/common/resourceLoader.ts @@ -106,7 +106,6 @@ function normalizeRequestPath(requestUri: URI) { return `${scheme}://`; // Url has own authority. } })); - console.log(requestUri, resourceUri); return resourceUri.with({ query: requestUri.query, fragment: requestUri.fragment diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index 9c0bf482e8..0f30ec4454 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -174,7 +174,7 @@ export class WebviewProtocolProvider extends Disposable { const fileService = { readFileStream: async (resource: URI): Promise => { - if (uri.scheme === Schemas.file) { + if (resource.scheme === Schemas.file) { return (await this.fileService.readFileStream(resource)).value; } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 9f83c2474d..36520b413f 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 23b218d661..0f5256fa87 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { // #region auth provider: https://github.com/microsoft/vscode/issues/88309 - export class AuthenticationSession { + export interface AuthenticationSession { /** * The identifier of the authentication session. */ @@ -39,8 +39,6 @@ declare module 'vscode' { * are defined by the authentication provider. */ readonly scopes: ReadonlyArray; - - constructor(id: string, accessToken: string, account: AuthenticationSessionAccountInformation, scopes: string[]); } /** @@ -104,7 +102,7 @@ declare module 'vscode' { clearSessionPreference?: boolean; } - export interface AuthenticationProviderAuthenticationSessionsChangeEvent extends AuthenticationSessionsChangeEvent { + export interface AuthenticationProviderAuthenticationSessionsChangeEvent { /** * The [authenticationProvider](#AuthenticationProvider) that has had its sessions change. */ @@ -212,17 +210,6 @@ declare module 'vscode' { */ export const providers: ReadonlyArray; - /** - * Returns whether a provider has any sessions matching the requested scopes. This request - * is transparent to the user, no UI is shown. Rejects if a provider with providerId is not - * registered. - * @param providerId The id of the provider - * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication - * provider - * @returns A thenable that resolve to whether the provider has sessions with the requested scopes. - */ - export function hasSessions(providerId: string, scopes: string[]): Thenable; - /** * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not * registered, or if the user does not consent to sharing authentication information with @@ -1436,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. @@ -1538,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 { @@ -1552,6 +1549,7 @@ declare module 'vscode' { } export interface NotebookConcatTextDocument { + uri: Uri; isClosed: boolean; dispose(): void; onDidChange: Event; @@ -1605,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. */ @@ -1834,10 +1837,27 @@ 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 { + viewType?: string; + filenamePattern?: GlobPattern; + excludeFileNamePattern?: GlobPattern; + } + + export interface NotebookKernelProvider { + onDidChangeKernels?: Event; + provideKernels(document: NotebookDocument, token: CancellationToken): ProviderResult; + resolveKernel?(kernel: T, document: NotebookDocument, webview: NotebookCommunication, token: CancellationToken): ProviderResult; } export namespace notebook { @@ -1846,6 +1866,11 @@ declare module 'vscode' { provider: NotebookContentProvider ): Disposable; + export function registerNotebookKernelProvider( + selector: NotebookDocumentFilter, + provider: NotebookKernelProvider + ): Disposable; + export function registerNotebookKernel( id: string, selectors: GlobPattern[], @@ -1882,6 +1907,8 @@ declare module 'vscode' { * @param selector */ export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; + + 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 87a9e6db87..df55287576 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 4be7ba2c78..c86985d018 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -3,14 +3,13 @@ * Licensed under the Source EULA. 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 } 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,9 +18,9 @@ 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'; export class MainThreadNotebookDocument extends Disposable { private _textModel: NotebookTextModel; @@ -54,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(); } } @@ -168,6 +133,7 @@ class DocumentAndEditorState { handle: cell.handle, uri: cell.uri, source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs, @@ -201,20 +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(); @@ -223,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; @@ -297,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({ @@ -330,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()), @@ -422,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 { @@ -455,37 +525,85 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { - let controller = this._notebookProviders.get(viewType); + async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise { + 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: 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) => { + return that._proxy.$executeNotebookKernelFromProvider(handle, uri, kernelId, cellHandle); + } + }); + this._notebookKernelProviders.set(handle, { + extension, + emitter, + provider + }); - if (controller) { - controller.updateLanguages(resource, languages); + return; + } + + async $unregisterNotebookKernelProvider(handle: number): Promise { + const entry = this._notebookKernelProviders.get(handle); + + if (entry) { + entry.emitter.dispose(); + entry.provider.dispose(); + this._notebookKernelProviders.delete(handle); } } + $onNotebookKernelChange(handle: number): void { + const entry = this._notebookKernelProviders.get(handle); + + entry?.emitter.fire(); + } + + async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { + 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 executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { - return this._proxy.$executeNotebook(viewType, uri, undefined, useAttachedKernel, 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 { @@ -499,218 +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 undefined; // {{SQL CARBON EDIT}} - } - - 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(), - 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 undefined; // {{SQL CARBON EDIT}} - } - - 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(), - 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 executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { - return this._mainThreadNotebook.executeNotebook(viewType, uri, useAttachedKernel, 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, useAttachedKernel: boolean, token: CancellationToken): Promise { - return this._proxy.$executeNotebook(this._viewType, uri, handle, useAttachedKernel, 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(); } } @@ -726,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 dfe8881c73..8300ea6bb6 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/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index c3e629d5b5..7a780a076b 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -84,12 +84,19 @@ interface IUserFriendlyViewDescriptor { icon?: string; contextualTitle?: string; + visibility?: string; // From 'remoteViewDescriptor' type group?: string; remoteName?: string | string[]; } +enum InitialVisibility { + Visible = 'visible', + Hidden = 'hidden', + Collapsed = 'collapsed' +} + const viewDescriptor: IJSONSchema = { type: 'object', properties: { @@ -113,6 +120,20 @@ const viewDescriptor: IJSONSchema = { description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"), type: 'string' }, + visibility: { + description: localize('vscode.extension.contributes.view.initialState', "Initial state of the view when the extension is first installed. Once the user has changed the view state by collapsing, moving, or hiding the view, the initial state will not be used again."), + type: 'string', + enum: [ + 'visible', + 'hidden', + 'collapsed' + ], + enumDescriptions: [ + localize('vscode.extension.contributes.view.initialState.visible', "The default initial state for view. The view will be expanded. This may have different behavior when the view container that the view is in is built in."), + localize('vscode.extension.contributes.view.initialState.hidden', "The view will not be shown in the view container, but will be discoverable through the views menu and other view entry points and can be un-hidden by the user."), + localize('vscode.extension.contributes.view.initialState.collapsed', "The view will show in the view container, but will be collapsed.") + ] + } } }; @@ -427,6 +448,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { : undefined; const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined; + const initialVisibility = this.convertInitialVisibility(item.visibility); const viewDescriptor = { id: item.id, name: item.name, @@ -437,12 +459,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { canToggleVisibility: true, canMoveView: true, treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name), - collapsed: this.showCollapsed(container), + collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed, order: order, extensionId: extension.description.identifier, originalContainerId: entry.key, group: item.group, - remoteAuthority: item.remoteName || (item).remoteAuthority // TODO@roblou - delete after remote extensions are updated + remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated + hideByDefault: initialVisibility === InitialVisibility.Hidden }; viewIds.add(viewDescriptor.id); @@ -471,6 +494,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } + private convertInitialVisibility(value: any): InitialVisibility | undefined { + if (Object.values(InitialVisibility).includes(value)) { + return value; + } + return undefined; + } + private isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean { if (!Array.isArray(viewDescriptors)) { collector.error(localize('requirearray', "views must be an array")); @@ -498,6 +528,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'contextualTitle')); return false; } + if (descriptor.visibility && !this.convertInitialVisibility(descriptor.visibility)) { + collector.error(localize('optenum', "property `{0}` can be omitted or must be one of {1}", 'visibility', Object.values(InitialVisibility).join(', '))); + return false; + } } return true; diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 7585048458..b3daa428a7 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; @@ -105,20 +105,11 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { if (removed.length) { - const removedDefaultConfigurations: IDefaultConfigurationExtension[] = removed.map(extension => { - const id = extension.description.identifier; - const name = extension.description.name; - const defaults = objects.deepClone(extension.value); - return { - id, name, defaults - }; - }); + const removedDefaultConfigurations = removed.map>(extension => objects.deepClone(extension.value)); configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations); } if (added.length) { - const addedDefaultConfigurations = added.map(extension => { - const id = extension.description.identifier; - const name = extension.description.name; + const addedDefaultConfigurations = added.map>(extension => { const defaults: IStringDictionary = objects.deepClone(extension.value); for (const key of Object.keys(defaults)) { if (!OVERRIDE_PROPERTY_PATTERN.test(key) || typeof defaults[key] !== 'object') { @@ -126,9 +117,7 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { delete defaults[key]; } } - return { - id, name, defaults - }; + return defaults; }); configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 841ae227ea..45c200651e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,9 +208,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get providers(): ReadonlyArray { return extHostAuthentication.providers; }, - hasSessions(providerId: string, scopes: string[]): Thenable { - return extHostAuthentication.hasSessions(providerId, scopes); - }, getSession(providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions) { return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, @@ -958,6 +955,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeVisibleNotebookEditors; }, + get onDidChangeActiveNotebookKernel() { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidChangeActiveNotebookKernel; + }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider); @@ -966,6 +967,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookKernel(extension, id, selector, kernel); }, + registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => { + checkProposedApiEnabled(extension); + return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider); + }, registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); @@ -1127,7 +1132,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CellKind: extHostTypes.CellKind, CellOutputKind: extHostTypes.CellOutputKind, NotebookCellRunState: extHostTypes.NotebookCellRunState, - AuthenticationSession: extHostTypes.AuthenticationSession + NotebookRunState: extHostTypes.NotebookRunState }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 71cfae369c..54c9232b02 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -169,7 +169,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>; @@ -601,6 +601,7 @@ export interface WebviewExtensionDescription { export interface NotebookExtensionDescription { readonly id: ExtensionIdentifier; readonly location: UriComponents; + readonly description?: string; } export enum WebviewEditorCapabilities { @@ -708,6 +709,9 @@ export interface MainThreadNotebookShape extends IDisposable { $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(id: string): Promise; $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise; + $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; + $unregisterNotebookKernelProvider(handle: number): Promise; + $onNotebookKernelChange(handle: number): void; $unregisterNotebookKernel(id: string): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; @@ -1615,12 +1619,17 @@ export interface INotebookDocumentsAndEditorsDelta { export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise; $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; - $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise; - $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): 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): 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/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 7dae881f29..50074d4c16 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -215,6 +215,20 @@ const newCommands: ApiCommand[] = [ [ApiCommandArgument.CallHierarchyItem], new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyOutgoingCall.to)) ), + // --- rename + new ApiCommand( + 'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position, new ApiCommandArgument('newName', 'The new symbol name', v => typeof v === 'string', v => v)], + new ApiCommandResult('A promise that resolves to a WorkspaceEdit.', value => { + if (!value) { + return undefined; + } + if (value.rejectReason) { + throw new Error(value.rejectReason); + } + return typeConverters.WorkspaceEdit.to(value); + }) + ) ]; @@ -238,15 +252,6 @@ export class ExtHostApiCommands { } registerCommands() { - this._register('vscode.executeDocumentRenameProvider', this._executeDocumentRenameProvider, { - description: 'Execute rename provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - { name: 'newName', description: 'The new symbol name', constraint: String } - ], - returns: 'A promise that resolves to a WorkspaceEdit.' - }); this._register('vscode.executeSignatureHelpProvider', this._executeSignatureHelpProvider, { description: 'Execute signature help provider.', args: [ @@ -376,23 +381,6 @@ export class ExtHostApiCommands { this._disposables.add(disposable); } - private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Promise { - const args = { - resource, - position: position && typeConverters.Position.from(position), - newName - }; - return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { - if (!value) { - return undefined; - } - if (value.rejectReason) { - return Promise.reject(new Error(value.rejectReason)); - } - return typeConverters.WorkspaceEdit.to(value); - }); - } - private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise { const args = { resource, diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 7b59cb8d73..199e218c98 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -37,26 +37,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } get providers(): ReadonlyArray { - return Object.freeze(this._providers); - } - - private async resolveSessions(providerId: string): Promise> { - const provider = this._authenticationProviders.get(providerId); - - let sessions; - if (!provider) { - sessions = await this._proxy.$getSessions(providerId); - } else { - sessions = await provider.getSessions(); - } - - return sessions; - } - - async hasSessions(providerId: string, scopes: string[]): Promise { - const orderedScopes = scopes.sort().join(' '); - const sessions = await this.resolveSessions(providerId); - return !!(sessions.filter(session => session.scopes.slice().sort().join(' ') === orderedScopes).length); + return Object.freeze(this._providers.slice()); } async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions & { createIfNone: true }): Promise; @@ -94,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 13e9a8a962..845c64ceb2 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -9,13 +9,14 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; +import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +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'; @@ -24,7 +25,6 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import { joinPath } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; import { hash } from 'vs/base/common/hash'; -import { generateUuid } from 'vs/base/common/uuid'; import { Cache } from './cache'; interface IObservable { @@ -54,64 +54,85 @@ interface INotebookEventEmitter { emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void; } -const addIdToOutput = (output: IRawOutput, id = generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich +const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich ? ({ ...output, outputId: id }) : output; +class DettachedCellDocumentData extends ExtHostDocumentData { + + private static readonly _fakeProxy = new class extends NotImplementedProxy('document') { + $trySaveDocument() { + return Promise.reject('Cell-document cannot be saved'); + } + }; + + constructor(cell: IMainCellDto) { + super(DettachedCellDocumentData._fakeProxy, + URI.revive(cell.uri), + cell.source, + cell.eol, + cell.language, + 0, + false + ); + } +} + export class ExtHostCell extends Disposable implements vscode.NotebookCell { - // private originalSource: string[]; - private _outputs: any[]; private _onDidChangeOutputs = new Emitter[]>(); - onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; - // private _textDocument: vscode.TextDocument | undefined; - // private _initalVersion: number = -1; - private _outputMapping = new WeakMap(); - private _metadata: vscode.NotebookCellMetadata; + readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; + private _outputs: any[]; + private _outputMapping = new WeakMap(); + + private _metadata: vscode.NotebookCellMetadata; private _metadataChangeListener: IDisposable; - private _documentData: ExtHostDocumentData; + readonly handle: number; + readonly uri: URI; + readonly cellKind: CellKind; - get document(): vscode.TextDocument { - return this._documentData.document; - } - - get notebook(): vscode.NotebookDocument { - return this._notebook; - } + // todo@jrieken this is a little fish because we have + // vscode.TextDocument for which we never fired an onDidOpen + // event and which doesn't appear in the list of documents. + // this will change once the "real" document comes along. We + // should come up with a better approach here... + readonly defaultDocument: DettachedCellDocumentData; constructor( - private readonly _notebook: ExtHostNotebookDocument, - readonly handle: number, - readonly uri: URI, - content: string, - public readonly cellKind: CellKind, - public language: string, - outputs: any[], - _metadata: vscode.NotebookCellMetadata | undefined, private _proxy: MainThreadNotebookShape, + readonly notebook: ExtHostNotebookDocument, + private _extHostDocument: ExtHostDocumentsAndEditors, + cell: IMainCellDto, ) { super(); - this._documentData = new ExtHostDocumentData( - new class extends NotImplementedProxy('document') { }, - uri, - content.split(/\r|\n|\r\n/g), '\n', - language, 0, false - ); - this._outputs = outputs; + this.handle = cell.handle; + this.uri = URI.revive(cell.uri); + this.cellKind = cell.cellKind; + this.defaultDocument = new DettachedCellDocumentData(cell); + + this._outputs = cell.outputs; for (const output of this._outputs) { this._outputMapping.set(output, output.outputId); delete output.outputId; } - const observableMetadata = getObservable(_metadata || {}); + const observableMetadata = getObservable(cell.metadata ?? {}); this._metadata = observableMetadata.proxy; this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this.updateMetadata(); + this._updateMetadata(); })); } + get document(): vscode.TextDocument { + return this._extHostDocument.getDocument(this.uri)?.document ?? this.defaultDocument.document; + } + + get language(): string { + return this.document.languageId; + } + get outputs() { return this._outputs; } @@ -131,7 +152,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { start: diff.start, toInsert: diff.toInsert.map((output): IProcessedOutput => { if (output.outputKind === CellOutputKind.Rich) { - const uuid = generateUuid(); + const uuid = UUID.generateUuid(); this._outputMapping.set(output, uuid); return { ...output, outputId: uuid }; } @@ -156,29 +177,14 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { const observableMetadata = getObservable(newMetadata); this._metadata = observableMetadata.proxy; this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this.updateMetadata(); + this._updateMetadata(); })); - this.updateMetadata(); + this._updateMetadata(); } - private updateMetadata(): Promise { - return this._proxy.$updateNotebookCellMetadata(this._notebook.viewType, this._notebook.uri, this.handle, this._metadata); - } - - attachTextDocument(document: ExtHostDocumentData) { - this._documentData = document; - // this._initalVersion = this._documentData.version; - } - - detachTextDocument() { - // no-op? keep stale document until new comes along? - - // if (this._textDocument && this._textDocument.version !== this._initalVersion) { - // this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g); - // } - // this._textDocument = undefined; - // this._initalVersion = -1; + private _updateMetadata(): Promise { + return this._proxy.$updateNotebookCellMetadata(this.notebook.viewType, this.notebook.uri, this.handle, this._metadata); } } @@ -367,12 +373,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo splices.reverse().forEach(splice => { let cellDtos = splice[2]; let newCells = cellDtos.map(cell => { - const extCell = new ExtHostCell(this, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy); - const documentData = this._documentsAndEditors.getDocument(URI.revive(cell.uri)); - if (documentData) { - extCell.attachTextDocument(documentData); - } + const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell); if (!this._cellDisposableMapping.has(extCell.handle)) { this._cellDisposableMapping.set(extCell.handle, new DisposableStore()); @@ -454,7 +456,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo private $changeCellLanguage(index: number, language: string): void { const cell = this.cells[index]; - cell.language = language; + cell.defaultDocument._acceptLanguageId(language); const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language }; this._emitter.emitCellLanguageChange(event); } @@ -480,20 +482,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo getCell2(cellUri: UriComponents) { return this.cells.find(cell => cell.uri.fragment === cellUri.fragment); } - - attachCellTextDocument(textDocument: ExtHostDocumentData) { - let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString()); - if (cell) { - cell.attachTextDocument(textDocument); - } - } - - detachCellTextDocument(textDocument: ExtHostDocumentData) { - let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString()); - if (cell) { - cell.detachTextDocument(); - } - } } export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit { @@ -629,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(); @@ -641,31 +639,8 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook private _proxy: MainThreadNotebookShape, private _webComm: vscode.NotebookCommunication, public document: ExtHostNotebookDocument, - private _documentsAndEditors: ExtHostDocumentsAndEditors ) { super(); - this._register(this._documentsAndEditors.onDidAddDocuments(documents => { - for (const documentData of documents) { - let data = CellUri.parse(documentData.document.uri); - if (data) { - if (this.document.uri.fsPath === data.notebook.fsPath) { - document.attachCellTextDocument(documentData); - } - } - } - })); - - this._register(this._documentsAndEditors.onDidRemoveDocuments(documents => { - for (const documentData of documents) { - let data = CellUri.parse(documentData.document.uri); - if (data) { - if (this.document.uri.fsPath === data.notebook.fsPath) { - document.detachCellTextDocument(documentData); - } - } - } - })); - this._register(this._webComm.onDidReceiveMessage(e => { this._onDidReceiveMessage.fire(e); })); @@ -727,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); } @@ -780,10 +758,124 @@ export interface ExtHostNotebookOutputRenderingHandler { findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[]; } +export class ExtHostNotebookKernelProviderAdapter extends Disposable { + private _kernelToId = new Map(); + private _idToKernel = new Map(); + constructor( + private readonly _proxy: MainThreadNotebookShape, + private readonly _handle: number, + private readonly _extension: IExtensionDescription, + private readonly _provider: vscode.NotebookKernelProvider + ) { + super(); + + if (this._provider.onDidChangeKernels) { + this._register(this._provider.onDidChangeKernels(() => { + this._proxy.$onNotebookKernelChange(this._handle); + })); + } + } + + async provideKernels(document: ExtHostNotebookDocument, token: vscode.CancellationToken): Promise { + 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) { + 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); + } + + newMap.set(kernel, id); + + return { + id, + label: kernel.label, + extension: this._extension.identifier, + extensionLocation: this._extension.extensionLocation, + description: kernel.description, + isPreferred: kernel.isPreferred, + preloads: kernel.preloads + }; + }); + + this._kernelToId = newMap; + + this._idToKernel.clear(); + this._kernelToId.forEach((value, key) => { + this._idToKernel.set(value, key); + }); + + 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); + + if (kernel && this._provider.resolveKernel) { + return this._provider.resolveKernel(kernel, document, webview, token); + } + } + + async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { + const kernel = this._idToKernel.get(kernelId); + + if (!kernel) { + return; + } + + if (cell) { + return withToken(token => (kernel.executeCell as any)(document, cell, token)); + } else { + 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 { + private static _notebookKernelProviderHandlePool: number = 0; + private readonly _proxy: MainThreadNotebookShape; private readonly _notebookContentProviders = new Map(); private readonly _notebookKernels = new Map(); + private readonly _notebookKernelProviders = new Map(); private readonly _documents = new Map(); private readonly _unInitializedDocuments = new Map(); private readonly _editors = new Map(); @@ -820,6 +912,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _onDidCloseNotebookDocument = new Emitter(); onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; visibleNotebookEditors: ExtHostNotebookEditor[] = []; + private _onDidChangeActiveNotebookKernel = new Emitter<{ document: ExtHostNotebookDocument, kernel: vscode.NotebookKernel | undefined }>(); + onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event; private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; @@ -864,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); @@ -989,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(); @@ -998,6 +1092,55 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } + registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) { + 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, 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, + }); + + return new extHostTypes.Disposable(() => { + adapter.dispose(); + this._notebookKernelProviders.delete(handle); + this._proxy.$unregisterNotebookKernelProvider(handle); + }); + } + + private _withAdapter(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise) { + const document = this._documents.get(URI.revive(uri).toString()); + + if (!document) { + return []; + } + + const provider = this._notebookKernelProviders.get(handle); + + if (!provider) { + return []; + } + + return callback(provider, document); + } + + async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise { + return this._withAdapter(handle, uri, (adapter, document) => { + return adapter.provideKernels(document, token); + }); + } + + async $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise { + await this._withAdapter(handle, uri, async (adapter, document) => { + let webComm = this._webviewComm.get(editorId); + + if (webComm) { + await adapter.resolveNotebook(kernelId, document, webComm.contentProviderComm, token); + } + }); + } + registerNotebookKernel(extension: IExtensionDescription, id: string, selectors: vscode.GlobPattern[], kernel: vscode.NotebookKernel): vscode.Disposable { if (this._notebookKernels.has(id)) { throw new Error(`Notebook kernel for '${id}' already registered`); @@ -1006,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); @@ -1101,7 +1244,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } - async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise { + async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { let document = this._documents.get(URI.revive(uri).toString()); if (!document) { @@ -1112,17 +1255,46 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; const provider = this._notebookContentProviders.get(viewType)!.provider; - if (provider.kernel && useAttachedKernel) { + 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 $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, 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); + }); + } + + 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) { @@ -1138,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)); } } @@ -1209,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(); @@ -1275,8 +1461,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN revivedUri, this._proxy, webComm.contentProviderComm, - document, - this._documentsAndEditors + document ); const cells = editor.document.cells; diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 9dd72e1750..56c9bf52d3 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -13,6 +13,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { score } from 'vs/editor/common/modes/languageSelector'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ResourceMap } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument { @@ -28,6 +30,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + readonly uri = URI.from({ scheme: 'vscode-concat-doc', path: generateUuid() }); + constructor( extHostNotebooks: ExtHostNotebookController, extHostDocuments: ExtHostDocuments, diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 447775cce8..8d82d15a6e 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 2a27f573db..b0996aee1c 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 @@ -2774,13 +2779,6 @@ export enum ExtensionMode { //#endregion ExtensionContext - -//#region Authentication -export class AuthenticationSession implements vscode.AuthenticationSession { - constructor(public id: string, public accessToken: string, public account: { label: string, id: string }, public scopes: string[]) { } -} - -//#endregion Authentication export enum StandardTokenType { Other = 0, Comment = 1, diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 2eb3769373..9862b63aee 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -40,7 +40,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 8983faa69d..69ffb27c8d 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/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index aa8091c2f2..3bc53c6ffa 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -256,7 +256,7 @@ export class BreadcrumbsControl { input = input.primary; } if (Registry.as(Extensions.EditorInputFactories).getFileEditorInputFactory().isFileEditorInput(input)) { - fileInfoUri = input.label; + fileInfoUri = input.preferredResource; } this.domNode.classList.toggle('hidden', false); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index a9a3173fe9..dfe7a795ca 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/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 7f8b46ddc2..2f4e475209 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -163,6 +163,8 @@ .monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge { margin-left: 8px; + display: flex; + align-items: center; } .monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge .badge-content { @@ -170,13 +172,14 @@ border-radius: 11px; font-size: 11px; min-width: 18px; - min-height: 18px; + height: 18px; line-height: 11px; font-weight: normal; text-align: center; display: inline-block; box-sizing: border-box; - } + position: relative; +} /* Rotate icons when panel is on right */ .monaco-workbench .part.panel.right .title-actions .codicon-split-horizontal, diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 7f1b33a45d..fb1dff879e 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -20,7 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class TreeViewPane extends ViewPane { - private treeView: ITreeView; + protected readonly treeView: ITreeView; constructor( options: IViewletViewOptions, @@ -55,7 +55,7 @@ export class TreeViewPane extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - this.treeView.show(container); + this.renderTreeView(container); } shouldShowWelcome(): boolean { @@ -64,13 +64,21 @@ export class TreeViewPane extends ViewPane { layoutBody(height: number, width: number): void { super.layoutBody(height, width); - this.treeView.layout(height, width); + this.layoutTreeView(height, width); } getOptimalWidth(): number { return this.treeView.getOptimalWidth(); } + protected renderTreeView(container: HTMLElement): void { + this.treeView.show(container); + } + + protected layoutTreeView(height: number, width: number): void { + this.treeView.layout(height, width); + } + private updateTreeVisibility(): void { this.treeView.setVisibility(this.isBodyVisible()); } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index a529b74b28..3ce94cb88c 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -982,6 +982,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.updateViewHeaders(); } }); + + this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => this._onTitleAreaUpdate.fire())); } getTitle(): string { diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 6ce50ee80d..38030cba3f 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -45,7 +45,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { @IConfigurationService protected configurationService: IConfigurationService ) { super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews)(() => { + this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews, viewPaneContainer.onTitleAreaUpdate)(() => { // Update title area since there is no better way to update secondary actions this.updateTitleArea(); })); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index e924394572..5557ea6f88 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/common/editor.ts b/src/vs/workbench/common/editor.ts index 51f1663759..21769343dc 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -167,7 +167,7 @@ export interface IFileEditorInputFactory { /** * Creates new new editor input capable of showing files. */ - createFileEditorInput(resource: URI, label: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; + createFileEditorInput(resource: URI, preferredResource: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; /** * Check if the provided object is a file editor input. @@ -649,20 +649,25 @@ export interface IModeSupport { export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeSupport { /** - * Gets the resource this file input is about. + * Gets the resource this file input is about. This will always be the + * canonical form of the resource, so it may differ from the original + * resource that was provided to create the input. Use `preferredResource` + * for the form as it was created. */ readonly resource: URI; /** - * Gets the label of the editor. In most cases this will - * be identical to the resource. + * Gets the preferred resource of the editor. In most cases this will + * be identical to the resource. But in some cases the preferredResource + * may differ in path casing to the actual resource because we keep + * canonical forms of resources in-memory. */ - readonly label: URI; + readonly preferredResource: URI; /** - * Sets the preferred label to use for this file input. + * Sets the preferred resource to use for this file input. */ - setLabel(label: URI): void; + setPreferredResource(preferredResource: URI): void; /** * Sets the preferred encoding to use for this file input. diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index 455fd09e2a..a4bf75b68b 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -22,12 +22,12 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { private static readonly MEMOIZER = createMemoizer(); - private _label: URI; - get label(): URI { return this._label; } + private _preferredResource: URI; + get preferredResource(): URI { return this._preferredResource; } constructor( public readonly resource: URI, - preferredLabel: URI | undefined, + preferredResource: URI | undefined, @IEditorService protected readonly editorService: IEditorService, @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @ITextFileService protected readonly textFileService: ITextFileService, @@ -37,7 +37,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { ) { super(); - this._label = preferredLabel || resource; + this._preferredResource = preferredResource || resource; this.registerListeners(); } @@ -51,7 +51,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { } private onLabelEvent(scheme: string): void { - if (scheme === this._label.scheme) { + if (scheme === this._preferredResource.scheme) { this.updateLabel(); } } @@ -65,25 +65,21 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { this._onDidChangeLabel.fire(); } - setLabel(label: URI): void { - if (!extUri.isEqual(label, this._label)) { - this._label = label; + setPreferredResource(preferredResource: URI): void { + if (!extUri.isEqual(preferredResource, this._preferredResource)) { + this._preferredResource = preferredResource; this.updateLabel(); } } - getLabel(): URI { - return this._label; - } - getName(): string { return this.basename; } @AbstractTextResourceEditorInput.MEMOIZER private get basename(): string { - return this.labelService.getUriBasenameLabel(this._label); + return this.labelService.getUriBasenameLabel(this._preferredResource); } getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined { @@ -100,17 +96,17 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { @AbstractTextResourceEditorInput.MEMOIZER private get shortDescription(): string { - return this.labelService.getUriBasenameLabel(dirname(this._label)); + return this.labelService.getUriBasenameLabel(dirname(this._preferredResource)); } @AbstractTextResourceEditorInput.MEMOIZER private get mediumDescription(): string { - return this.labelService.getUriLabel(dirname(this._label), { relative: true }); + return this.labelService.getUriLabel(dirname(this._preferredResource), { relative: true }); } @AbstractTextResourceEditorInput.MEMOIZER private get longDescription(): string { - return this.labelService.getUriLabel(dirname(this._label)); + return this.labelService.getUriLabel(dirname(this._preferredResource)); } @AbstractTextResourceEditorInput.MEMOIZER @@ -120,12 +116,12 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { @AbstractTextResourceEditorInput.MEMOIZER private get mediumTitle(): string { - return this.labelService.getUriLabel(this._label, { relative: true }); + return this.labelService.getUriLabel(this._preferredResource, { relative: true }); } @AbstractTextResourceEditorInput.MEMOIZER private get longTitle(): string { - return this.labelService.getUriLabel(this._label); + return this.labelService.getUriLabel(this._preferredResource); } getTitle(verbosity: Verbosity): string { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 4b93f36d66..42c8cbd265 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -618,6 +618,8 @@ export interface ITreeItemLabel { highlights?: [number, number][]; + strikethrough?: boolean; + } export interface ITreeItem { diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index cef709ab29..836be5e8a9 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -160,7 +160,9 @@ export class FileBasedRecommendations extends ExtensionRecommendations { if (match(pattern, uri.toString())) { for (const extensionId of extensionIds) { // Add to recommendation to prompt if it is an important tip - if (this.importantExtensionTips[extensionId]) { + // Only prompt if the pattern matches the extensionImportantTips pattern + // Otherwise, assume pattern is from extensionTips, which means it should be a file based "passive" recommendation + if (this.importantExtensionTips[extensionId]?.pattern === pattern) { recommendationsToPrompt.push(extensionId); } // Update file based recommendations diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 7dff52c25a..37ec9e6d59 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'; @@ -102,8 +102,8 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register default file input factory Registry.as(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({ - createFileEditorInput: (resource, label, encoding, mode, instantiationService): IFileEditorInput => { - return instantiationService.createInstance(FileEditorInput, resource, label, encoding, mode); + createFileEditorInput: (resource, preferredResource, encoding, mode, instantiationService): IFileEditorInput => { + return instantiationService.createInstance(FileEditorInput, resource, preferredResource, encoding, mode); }, isFileEditorInput: (obj): obj is IFileEditorInput => { @@ -113,7 +113,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor interface ISerializedFileEditorInput { resourceJSON: UriComponents; - labelJSON?: UriComponents; + preferredResourceJSON?: UriComponents; encoding?: string; modeId?: string; } @@ -128,10 +128,10 @@ class FileEditorInputFactory implements IEditorInputFactory { serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; const resource = fileEditorInput.resource; - const label = fileEditorInput.getLabel(); + const preferredResource = fileEditorInput.preferredResource; const serializedFileEditorInput: ISerializedFileEditorInput = { resourceJSON: resource.toJSON(), - labelJSON: extUri.isEqual(resource, label) ? undefined : label, // only storing label if it differs from the resource + preferredResourceJSON: extUri.isEqual(resource, preferredResource) ? undefined : preferredResource, // only storing preferredResource if it differs from the resource encoding: fileEditorInput.getEncoding(), modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data }; @@ -143,13 +143,13 @@ class FileEditorInputFactory implements IEditorInputFactory { return instantiationService.invokeFunction(accessor => { const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput); const resource = URI.revive(serializedFileEditorInput.resourceJSON); - const label = URI.revive(serializedFileEditorInput.labelJSON); + const preferredResource = URI.revive(serializedFileEditorInput.preferredResourceJSON); const encoding = serializedFileEditorInput.encoding; const mode = serializedFileEditorInput.modeId; const fileEditorInput = accessor.get(IEditorService).createEditorInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; - if (label) { - fileEditorInput.setLabel(label); + if (preferredResource) { + fileEditorInput.setPreferredResource(preferredResource); } return fileEditorInput; diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 1cd73a811b..b86f870d1e 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -45,7 +45,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements constructor( resource: URI, - preferredLabel: URI | undefined, + preferredResource: URI | undefined, preferredEncoding: string | undefined, preferredMode: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -57,7 +57,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(resource, preferredLabel, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService); + super(resource, preferredResource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService); this.model = this.textFileService.files.get(resource); 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 e004d53846..a36406162f 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -9,7 +9,7 @@ import { toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { workbenchInstantiationService, TestServiceAccessor, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EncodingMode, Verbosity } from 'vs/workbench/common/editor'; +import { EncodingMode, IEditorInputFactoryRegistry, Verbosity, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; @@ -18,6 +18,7 @@ import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRe import { DisposableStore } from 'vs/base/common/lifecycle'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; suite('Files - FileEditorInput', () => { let instantiationService: IInstantiationService; @@ -92,18 +93,32 @@ suite('Files - FileEditorInput', () => { } }); - test('label', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined, undefined); + test('preferred resource', function () { + const resource = toResource.call(this, '/foo/bar/updatefile.js'); + const preferredResource = toResource.call(this, '/foo/bar/UPDATEFILE.js'); + + const inputWithoutPreferredResource = instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined); + assert.equal(inputWithoutPreferredResource.resource.toString(), resource.toString()); + assert.equal(inputWithoutPreferredResource.preferredResource.toString(), resource.toString()); + + const inputWithPreferredResource = instantiationService.createInstance(FileEditorInput, resource, preferredResource, undefined, undefined); + + assert.equal(inputWithPreferredResource.resource.toString(), resource.toString()); + assert.equal(inputWithPreferredResource.preferredResource.toString(), preferredResource.toString()); let didChangeLabel = false; - const listener = input.onDidChangeLabel(e => { + const listener = inputWithPreferredResource.onDidChangeLabel(e => { didChangeLabel = true; }); - assert.equal(input.getName(), 'UPDATEFILE.js'); + assert.equal(inputWithPreferredResource.getName(), 'UPDATEFILE.js'); - input.setLabel(toResource.call(this, '/FOO/BAR/updateFILE.js')); - assert.equal(input.getName(), 'updateFILE.js'); + const otherPreferredResource = toResource.call(this, '/FOO/BAR/updateFILE.js'); + inputWithPreferredResource.setPreferredResource(otherPreferredResource); + + assert.equal(inputWithPreferredResource.resource.toString(), resource.toString()); + assert.equal(inputWithPreferredResource.preferredResource.toString(), otherPreferredResource.toString()); + assert.equal(inputWithPreferredResource.getName(), 'updateFILE.js'); assert.equal(didChangeLabel, true); listener.dispose(); @@ -239,4 +254,37 @@ suite('Files - FileEditorInput', () => { resolved.dispose(); }); + + test('file editor input factory', async function () { + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined); + + const factory = Registry.as(EditorExtensions.EditorInputFactories).getEditorInputFactory(input.getTypeId()); + if (!factory) { + assert.fail('File Editor Input Factory missing'); + } + + assert.equal(factory.canSerialize(input), true); + + const inputSerialized = factory.serialize(input); + if (!inputSerialized) { + assert.fail('Unexpected serialized file input'); + } + + const inputDeserialized = factory.deserialize(instantiationService, inputSerialized); + assert.equal(input.matches(inputDeserialized), true); + + const preferredResource = toResource.call(this, '/foo/bar/UPDATEfile.js'); + const inputWithPreferredResource = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), preferredResource, undefined, undefined); + + const inputWithPreferredResourceSerialized = factory.serialize(inputWithPreferredResource); + if (!inputWithPreferredResourceSerialized) { + assert.fail('Unexpected serialized file input'); + } + + const inputWithPreferredResourceDeserialized = factory.deserialize(instantiationService, inputWithPreferredResourceSerialized) as FileEditorInput; + 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 4c355a5aa9..24515de147 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 40b3ffa75c..32279bc586 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/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index df3e09994f..99633472c1 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -5,9 +5,9 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersModel, compareMarkersByUri } from './markersModel'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; -import { NumberBadge, ViewContaierActivityByView } from 'vs/workbench/services/activity/common/activity'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { localize } from 'vs/nls'; import Constants from './constants'; import { URI } from 'vs/base/common/uri'; @@ -55,22 +55,21 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb export class ActivityUpdater extends Disposable implements IWorkbenchContribution { - private readonly activity: ViewContaierActivityByView; + private readonly activity = this._register(new MutableDisposable()); constructor( - @IInstantiationService instantiationService: IInstantiationService, + @IActivityService private readonly activityService: IActivityService, @IMarkerService private readonly markerService: IMarkerService ) { super(); - this.activity = this._register(instantiationService.createInstance(ViewContaierActivityByView, Constants.MARKERS_VIEW_ID)); - this._register(this.markerService.onMarkerChanged(() => this.updateActivity())); - this.updateActivity(); + this._register(this.markerService.onMarkerChanged(() => this.updateBadge())); + this.updateBadge(); } - private updateActivity(): void { + private updateBadge(): void { const { errors, warnings, infos } = this.markerService.getStatistics(); const total = errors + warnings + infos; const message = localize('totalProblems', 'Total {0} Problems', total); - this.activity.setActivity({ badge: new NumberBadge(total, () => message) }); + this.activity.value = this.activityService.showViewActivity(Constants.MARKERS_VIEW_ID, { badge: new NumberBadge(total, () => message) }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index f55622270e..53033bd71c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -86,6 +86,17 @@ const enum CellToolbarOrder { DeleteCell } +export interface INotebookActionContext { + readonly cellTemplate?: BaseCellRenderTemplate; + readonly cell?: ICellViewModel; + readonly notebookEditor: INotebookEditor; + readonly ui?: boolean; +} + +export interface INotebookCellActionContext extends INotebookActionContext { + cell: ICellViewModel; +} + abstract class NotebookAction extends Action2 { constructor(desc: IAction2Options) { if (desc.f1 !== false) { @@ -112,9 +123,9 @@ abstract class NotebookAction extends Action2 { super(desc); } - async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { - if (!this.isCellActionContext(context)) { - context = this.getActiveCellContext(accessor); + async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { + if (!this.isNotebookActionContext(context)) { + context = this.getActiveEditorContext(accessor); if (!context) { return; } @@ -123,13 +134,13 @@ abstract class NotebookAction extends Action2 { this.runWithContext(accessor, context); } - abstract async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; + abstract async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise; - private isCellActionContext(context?: INotebookCellActionContext): context is INotebookCellActionContext { - return !!context && !!context.cell && !!context.notebookEditor; + private isNotebookActionContext(context?: unknown): context is INotebookActionContext { + return !!context && !!(context as INotebookActionContext).notebookEditor; } - private getActiveCellContext(accessor: ServicesAccessor): INotebookCellActionContext | undefined { + protected getActiveEditorContext(accessor: ServicesAccessor): INotebookActionContext | undefined { const editorService = accessor.get(IEditorService); const editor = getActiveNotebookEditor(editorService); @@ -149,7 +160,28 @@ abstract class NotebookAction extends Action2 { } } -registerAction2(class extends NotebookAction { +abstract class NotebookCellAction extends NotebookAction { + protected isCellActionContext(context?: unknown): context is INotebookCellActionContext { + return !!context && !!(context as INotebookCellActionContext).notebookEditor && !!(context as INotebookCellActionContext).cell; + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!this.isCellActionContext(context)) { + const activeEditorContext = this.getActiveEditorContext(accessor); + if (this.isCellActionContext(activeEditorContext)) { + context = activeEditorContext; + } else { + return; + } + } + + this.runWithContext(accessor, context); + } + + abstract async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; +} + +registerAction2(class extends NotebookCellAction { constructor() { super({ id: EXECUTE_CELL_COMMAND_ID, @@ -171,7 +203,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: CANCEL_CELL_COMMAND_ID, @@ -222,7 +254,7 @@ export class CancelCellAction extends MenuItemAction { } -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: EXECUTE_CELL_SELECT_BELOW, @@ -258,7 +290,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: EXECUTE_CELL_INSERT_BELOW, @@ -292,7 +324,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { renderAllMarkdownCells(context); } }); @@ -305,7 +337,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { renderAllMarkdownCells(context); const editorGroupService = accessor.get(IEditorGroupsService); @@ -321,7 +353,7 @@ registerAction2(class extends NotebookAction { } }); -function renderAllMarkdownCells(context: INotebookCellActionContext): void { +function renderAllMarkdownCells(context: INotebookActionContext): void { context.notebookEditor.viewModel!.viewCells.forEach(cell => { if (cell.cellKind === CellKind.Markdown) { cell.editState = CellEditState.Preview; @@ -337,7 +369,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { return context.notebookEditor.cancelNotebookExecution(); } }); @@ -364,7 +396,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK) }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: CHANGE_CELL_TO_CODE_COMMAND_ID, @@ -383,7 +415,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID, @@ -456,13 +488,6 @@ export async function changeCellToKind(kind: CellKind, context: INotebookCellAct return newCell; } -export interface INotebookCellActionContext { - readonly cellTemplate?: BaseCellRenderTemplate; - readonly cell: ICellViewModel; - readonly notebookEditor: INotebookEditor; - readonly ui?: boolean; -} - abstract class InsertCellCommand extends NotebookAction { constructor( desc: Readonly, @@ -472,7 +497,7 @@ abstract class InsertCellCommand extends NotebookAction { super(desc); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { const newCell = context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction, undefined, context.ui); if (newCell) { context.notebookEditor.focusNotebookCell(newCell, 'editor'); @@ -558,7 +583,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, { group: 'inline' }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -587,7 +612,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -624,7 +649,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -657,12 +682,6 @@ registerAction2(class extends NotebookAction { const nextCellIdx = index < context.notebookEditor.viewModel!.length ? index : context.notebookEditor.viewModel!.length - 1; if (nextCellIdx >= 0) { context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel!.viewCells[nextCellIdx], 'container'); - } else { - // No cells left, insert a new empty one - const newCell = context.notebookEditor.insertNotebookCell(undefined, context.cell.cellKind); - if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, 'editor'); - } } } } @@ -688,7 +707,7 @@ async function copyCell(context: INotebookCellActionContext, direction: 'up' | ' } } -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -708,7 +727,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -728,7 +747,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -750,7 +769,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -779,7 +798,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -848,7 +867,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { const notebookService = accessor.get(INotebookService); const pasteCells = notebookService.getToCopy(); @@ -862,7 +881,7 @@ registerAction2(class extends NotebookAction { return; } - const currCellIndex = viewModel.getCellIndex(context!.cell); + const currCellIndex = context.cell && viewModel.getCellIndex(context.cell); let topPastedCell: CellViewModel | undefined = undefined; pasteCells.items.reverse().map(cell => { @@ -880,8 +899,8 @@ registerAction2(class extends NotebookAction { return cell; } }).forEach(pasteCell => { - topPastedCell = viewModel.insertCell(currCellIndex + 1, pasteCell, true); - return; + const newIdx = typeof currCellIndex === 'number' ? currCellIndex + 1 : 0; + topPastedCell = viewModel.insertCell(newIdx, pasteCell, true); }); if (topPastedCell) { @@ -890,7 +909,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -909,7 +928,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -928,7 +947,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: NOTEBOOK_FOCUS_NEXT_EDITOR, @@ -975,7 +994,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: NOTEBOOK_FOCUS_PREVIOUS_EDITOR, @@ -1019,7 +1038,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: FOCUS_IN_OUTPUT_COMMAND_ID, @@ -1040,7 +1059,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: FOCUS_OUT_OUTPUT_COMMAND_ID, @@ -1075,7 +1094,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { await context.notebookEditor.viewModel?.undo(); } }); @@ -1093,7 +1112,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { await context.notebookEditor.viewModel?.redo(); } }); @@ -1112,7 +1131,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { const editor = context.notebookEditor; if (!editor.viewModel || !editor.viewModel.length) { return; @@ -1137,7 +1156,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { const editor = context.notebookEditor; if (!editor.viewModel || !editor.viewModel.length) { return; @@ -1148,7 +1167,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: CLEAR_CELL_OUTPUTS_COMMAND_ID, @@ -1183,7 +1202,7 @@ interface ILanguagePickInput extends IQuickPickItem { description: string; } -export class ChangeCellLanguageAction extends NotebookAction { +export class ChangeCellLanguageAction extends NotebookCellAction { constructor() { super({ id: CHANGE_CELL_LANGUAGE, @@ -1293,7 +1312,7 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { const editor = context.notebookEditor; if (!editor.viewModel || !editor.viewModel.length) { return; @@ -1304,15 +1323,13 @@ 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'); } } -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -1320,13 +1337,13 @@ registerAction2(class extends NotebookAction { 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 }, @@ -1340,13 +1357,13 @@ registerAction2(class extends NotebookAction { 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'); } } -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -1365,7 +1382,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super( { @@ -1384,7 +1401,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookAction { +registerAction2(class extends NotebookCellAction { constructor() { super({ id: CENTER_ACTIVE_CELL, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index a6cfc373dc..62f5d7ba77 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -140,7 +140,8 @@ registerAction2(class extends Action2 { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET, mac: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_OPEN_SQUARE_BRACKET + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_OPEN_SQUARE_BRACKET, + secondary: [KeyCode.LeftArrow], }, secondary: [KeyCode.LeftArrow], weight: KeybindingWeight.WorkbenchContrib @@ -183,7 +184,8 @@ registerAction2(class extends Action2 { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET, mac: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_CLOSE_SQUARE_BRACKET + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_CLOSE_SQUARE_BRACKET, + secondary: [KeyCode.RightArrow], }, secondary: [KeyCode.RightArrow], weight: KeybindingWeight.WorkbenchContrib 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 c870050332..94ce77aee8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -4,13 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { INotebookCellActionContext, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { INotebookEditor, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookActionContext, NOTEBOOK_ACTIONS_CATEGORY, getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { INotebookKernelInfo2, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +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 { @@ -21,20 +31,15 @@ registerAction2(class extends Action2 { title: { value: nls.localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, icon: { id: 'codicon/server-environment' }, - menu: { - id: MenuId.EditorTitle, - when: NOTEBOOK_HAS_MULTIPLE_KERNELS, - group: 'navigation', - order: -2, - }, f1: true }); } - async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise { 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) { @@ -43,18 +48,31 @@ registerAction2(class extends Action2 { const editor = editorService.activeEditorPane?.getControl() as INotebookEditor; const activeKernel = editor.activeKernel; + 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[] = availableKernels.map((a) => { + const picks: QuickPickInput[] = [...availableKernels2, ...availableKernels].map((a) => { return { id: a.id, label: a.label, picked: a.id === activeKernel?.id, - description: a.extension.value + (a.id === activeKernel?.id - ? nls.localize('currentActiveKernel', " (Currently Active)") - : ''), - run: () => { + description: + (a as INotebookKernelInfo2).description + ? (a as INotebookKernelInfo2).description + : 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) + }] }; }); @@ -68,14 +86,109 @@ 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 }); - 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(); } }); + +export class KernelStatus extends Disposable implements IWorkbenchContribution { + private _editorDisposable = new DisposableStore(); + private readonly kernelInfoElement = this._register(new MutableDisposable()); + constructor( + @IEditorService private readonly _editorService: IEditorService, + @INotebookService private readonly _notebookService: INotebookService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, + ) { + super(); + this.registerListeners(); + } + + registerListeners() { + this._register(this._editorService.onDidActiveEditorChange(() => this.updateStatusbar())); + this._register(this._notebookService.onDidChangeActiveEditor(() => this.updateStatusbar())); + this._register(this._notebookService.onDidChangeKernels(() => this.updateStatusbar())); + } + + updateStatusbar() { + this._editorDisposable.clear(); + + const activeEditor = getActiveNotebookEditor(this._editorService); + + if (activeEditor && activeEditor.multipleKernelsAvailable) { + this.showKernelStatus(activeEditor.activeKernel); + this._editorDisposable.add(activeEditor.onDidChangeKernel(() => { + if (activeEditor.multipleKernelsAvailable) { + this.showKernelStatus(activeEditor.activeKernel); + } else { + this.kernelInfoElement.clear(); + } + })); + } else { + this.kernelInfoElement.clear(); + } + } + + showKernelStatus(kernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined) { + this.kernelInfoElement.value = this._statusbarService.addEntry({ + text: kernel ? kernel.label : 'Choose Kernel', + ariaLabel: kernel ? kernel.label : 'Choose Kernel', + tooltip: nls.localize('chooseActiveKernel', "Choose kernel for current notebook"), + command: 'notebook.selectKernel', + }, 'notebook.selectKernel', nls.localize('notebook.selectKernel', "Choose kernel for current notebook"), StatusbarAlignment.RIGHT, 100); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(KernelStatus, LifecyclePhase.Ready); + diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 5c50862266..2452ae4e2e 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'; @@ -23,9 +22,10 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { CellLanguageStatusBarItem, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { IMenu } from 'vs/platform/actions/common/actions'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); @@ -65,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; @@ -74,6 +81,7 @@ export interface CodeCellLayoutInfo { readonly outputTotalHeight: number; readonly indicatorHeight: number; readonly bottomToolbarOffset: number; + readonly layoutState: CodeCellLayoutState; } export interface CodeCellLayoutChangeEvent { @@ -108,7 +116,6 @@ export interface ICellViewModel { language: string; cellKind: CellKind; editState: CellEditState; - currentTokenSource: CancellationTokenSource | undefined; focusMode: CellFocusMode; getText(): string; getTextLength(): number; @@ -171,7 +178,8 @@ export interface INotebookEditor extends IEditor { readonly onDidChangeModel: Event; readonly onDidFocusEditorWidget: Event; isNotebookEditor: boolean; - activeKernel: INotebookKernelInfo | undefined; + activeKernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined; + multipleKernelsAvailable: boolean; readonly onDidChangeKernel: Event; isDisposed: boolean; @@ -451,6 +459,7 @@ export interface BaseCellRenderTemplate { currentRenderedCell?: ICellViewModel; statusBarContainer: HTMLElement; languageStatusBarItem: CellLanguageStatusBarItem; + titleMenu: IMenu; toJSON: () => object; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 92dc989669..c05d315d33 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -102,6 +102,7 @@ export class NotebookEditor extends BaseEditor { setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { super.setEditorVisible(visible, group); if (group) { + this._groupListener.clear(); this._groupListener.add(group.onWillCloseEditor(e => this._saveEditorViewState(e.editor))); this._groupListener.add(group.onDidGroupChange(() => { if (this._editorGroupService.activeGroup !== group) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index f8df68dbbc..a328ad50e3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -28,7 +28,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED, INotebookDeltaDecoration, NOTEBOOK_CELL_LIST_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; @@ -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 } 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.$; @@ -84,13 +86,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _dimension: DOM.Dimension | null = null; private _shadowElementViewInfo: { height: number, width: number, top: number; left: number; } | null = null; - private _cellListFocusTracker: DOM.IFocusTracker | null = null; private _editorFocus: IContextKey | null = null; - private _cellListFocus: IContextKey | null = null; 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; }; @@ -132,7 +132,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._notebookViewModel?.notebookDocument; } - private _activeKernel: INotebookKernelInfo | undefined = undefined; + private _activeKernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined = undefined; private readonly _onDidChangeKernel = this._register(new Emitter()); readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; @@ -140,7 +140,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._activeKernel; } - set activeKernel(kernel: INotebookKernelInfo | undefined) { + set activeKernel(kernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined) { if (this._isDisposed) { return; } @@ -149,6 +149,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._onDidChangeKernel.fire(); } + private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined; + multipleKernelsAvailable: boolean = false; + private readonly _onDidChangeActiveEditor = this._register(new Emitter()); readonly onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; @@ -198,6 +201,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.notebookService.addNotebookEditor(this); } + /** + * EditorId + */ public getId(): string { return this._uuid; } @@ -232,12 +238,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // Note - focus going to the webview will fire 'blur', but the webview element will be // a descendent of the notebook editor root. const focused = DOM.isAncestor(document.activeElement, this._overlayContainer); - if (focused) { - const cellListFocused = DOM.isAncestor(document.activeElement, this._body); - this._cellListFocus?.set(cellListFocused); - } else { - this._cellListFocus?.set(false); - } this._editorFocus?.set(focused); this._notebookViewModel?.setFocus(focused); } @@ -258,14 +258,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._createBody(this._overlayContainer); this._generateFontInfo(); this._editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService); - this._cellListFocus = NOTEBOOK_CELL_LIST_FOCUSED.bindTo(this.contextKeyService); this._isVisible = true; this._outputFocus = NOTEBOOK_OUTPUT_FOCUSED.bindTo(this.contextKeyService); this._editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService); 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); @@ -396,17 +395,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._register(widgetFocusTracker); this._register(widgetFocusTracker.onDidFocus(() => this._onDidFocusEmitter.fire())); - this._cellListFocusTracker = this._register(DOM.trackFocus(this._body)); - this._register(this._cellListFocusTracker.onDidFocus(() => { - // 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(); - }, 0); - })); - this._register(this._cellListFocusTracker.onDidBlur(() => { - this.updateEditorFocus(); - })); } private _updateForCursorNavigationMode(applyFocusChange: () => void): void { @@ -477,12 +465,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // clear state this._dndController?.clearGlobalDragState(); - this._setKernels(textModel); + this._currentKernelTokenSource = new CancellationTokenSource(); + this._localStore.add(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(() => { - if (this.activeKernel === undefined) { - this._setKernels(textModel); - } + this._localStore.add(this.notebookService.onDidChangeKernels(async () => { + this._currentKernelTokenSource?.cancel(); + this._currentKernelTokenSource = new CancellationTokenSource(); + await this._setKernels(textModel, this._currentKernelTokenSource); })); this._localStore.add(this._list!.onDidChangeFocus(() => { @@ -553,29 +544,100 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._list?.clear(); } - private _setKernels(textModel: NotebookTextModel) { + private async _setKernels(textModel: NotebookTextModel, tokenSource: CancellationTokenSource) { const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri); + const availableKernels2 = await this.notebookService.getContributedNotebookKernels2(textModel.viewType, textModel.uri, tokenSource.token); - if (provider.kernel && availableKernels.length > 0) { - this._notebookHasMultipleKernels!.set(true); - } else if (availableKernels.length > 1) { - this._notebookHasMultipleKernels!.set(true); - } else { - this._notebookHasMultipleKernels!.set(false); + 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; + } else if ((availableKernels.length + availableKernels2.length) > 1) { + this._notebookHasMultipleKernels!.set(true); + this.multipleKernelsAvailable = true; + } else { + this._notebookHasMultipleKernels!.set(false); + 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; } + 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 as INotebookKernelInfo2).resolve(this.viewModel!.uri, this.getId(), tokenSource.token); // {{SQL CARBON EDIT}} strict-null-checks + } + + 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) { @@ -585,10 +647,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 { @@ -1106,14 +1171,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 { @@ -1124,43 +1201,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) { - 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, true, 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 { - return await this.notebookService.executeNotebook(viewType, notebookUri, false, tokenSource.token); + 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 { @@ -1173,33 +1261,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) { - 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, true, 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, cell.handle); } else { - return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, false, 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 0000000000..cc230a954a --- /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 Source EULA. 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 e651c8727f..01c5963ff7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { notebookProviderExtensionPoint, notebookRendererExtensionPoint, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; 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 } 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'; @@ -28,11 +28,52 @@ import { Memento } from 'vs/workbench/common/memento'; import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; 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'; @@ -52,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 @@ -59,6 +102,8 @@ export class NotebookProviderInfoStore extends Disposable { this.clear(); mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = []; this._memento.saveMemento(); + + this._updateProviderExtensionsInfo(); } })); } @@ -74,6 +119,8 @@ export class NotebookProviderInfoStore extends Disposable { displayName: notebookContribution.displayName, selector: notebookContribution.selector || [], priority: this._convertPriority(notebookContribution.priority), + 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 })); @@ -83,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) { @@ -164,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()); @@ -189,6 +255,8 @@ 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; @@ -199,7 +267,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu @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(); @@ -297,6 +366,47 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._onDidChangeKernels.fire(); } + registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { + const d = this.notebookKernelProviderInfoStore.add(provider); + const kernelChangeEventListener = provider.onDidChangeKernels(() => { + this._onDidChangeKernels.fire(); + }); + return toDisposable(() => { + kernelChangeEventListener.dispose(); + d.dispose(); + }); + } + + async getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise { + const filteredProvider = this.notebookKernelProviderInfoStore.get(viewType, resource); + const result = new Array(filteredProvider.length); + + const promises = filteredProvider.map(async (provider, index) => { + const data = await provider.provideKernels(resource, token); + result[index] = data.map(dto => { + return { + extension: dto.extension, + extensionLocation: dto.extensionLocation, + id: dto.id, + label: dto.label, + description: dto.description, + isPreferred: dto.isPreferred, + preloads: dto.preloads, + resolve: async (uri: URI, editorId: string, token: CancellationToken) => { + return provider.resolveKernel(editorId, uri, dto.id, token); + }, + executeNotebookCell: async (uri: URI, handle: number | undefined) => { + return provider.executeNotebook(uri, dto.id, handle); + } + }; + }); + }); + + await Promise.all(promises); + + return flatten(result); + } + getContributedNotebookKernels(viewType: string, resource: URI): INotebookKernelInfo[] { let kernelInfos: INotebookKernelInfo[] = []; this._notebookKernels.forEach(kernel => { @@ -343,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), @@ -397,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[], @@ -630,34 +736,51 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } - async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { + async executeNotebook(viewType: string, uri: URI): Promise { let provider = this._notebookProviders.get(viewType); if (provider) { - return provider.controller.executeNotebook(viewType, uri, useAttachedKernel, token); + return provider.controller.executeNotebookByAttachedKernel(viewType, uri); } return; } - async executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, 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, useAttachedKernel, 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); } } @@ -721,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); } @@ -800,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/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 46ede5e218..cda0a8f777 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -19,7 +19,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellRange, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState, CellFocusMode, BaseCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellRange, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState, CellFocusMode, BaseCellRenderTemplate, NOTEBOOK_CELL_LIST_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { diff, IProcessedOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { clamp } from 'vs/base/common/numbers'; @@ -75,6 +75,7 @@ export class NotebookCellList extends WorkbenchList implements ID @IKeybindingService keybindingService: IKeybindingService ) { super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); + NOTEBOOK_CELL_LIST_FOCUSED.bindTo(this.contextKeyService).set(true); this._focusNextPreviousDelegate = options.focusNextPreviousDelegate; this._previousFocusedElements = this.getFocusedElements(); this._localDisposableStore.add(this.onDidChangeFocus((e) => { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index a38566ad1b..53319f6c66 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -268,14 +268,49 @@ export class BackLayerWebView extends Disposable { box-sizing: border-box; background-color: var(--vscode-notebook-outputContainerBackgroundColor); } + #container > div.nb-symbolHighlight > div { background-color: var(--vscode-notebook-symbolHighlightBackground); } + body { padding: 0px; height: 100%; width: 100%; } + + table, thead, tr, th, td, tbody { + border: none !important; + border-color: transparent; + border-spacing: 0; + border-collapse: collapse; + } + + table { + width: 100%; + } + + table, th, tr { + text-align: left !important; + } + + thead { + font-weight: bold; + background-color: rgba(130, 130, 130, 0.16); + } + + th, td { + padding: 4px 8px; + } + + tr:nth-child(even) { + background-color: rgba(130, 130, 130, 0.08); + } + + tbody th { + font-weight: normal; + } + diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 413a0f6d76..c6ba378683 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -170,7 +170,8 @@ export class CellEditorOptions { } abstract class AbstractCellRenderer { - protected editorOptions: CellEditorOptions; + protected readonly editorOptions: CellEditorOptions; + protected readonly cellMenus: CellMenus; constructor( protected readonly instantiationService: IInstantiationService, @@ -184,6 +185,7 @@ abstract class AbstractCellRenderer { protected readonly dndController: CellDragAndDropController ) { this.editorOptions = new CellEditorOptions(configurationService, language); + this.cellMenus = this.instantiationService.createInstance(CellMenus); } dispose() { @@ -259,12 +261,9 @@ abstract class AbstractCellRenderer { return { primary, secondary }; } - protected setupCellToolbarActions(scopedContextKeyService: IContextKeyService, templateData: BaseCellRenderTemplate, disposables: DisposableStore): void { - const cellMenu = this.instantiationService.createInstance(CellMenus); - const menu = disposables.add(cellMenu.getCellTitleMenu(scopedContextKeyService)); - + protected setupCellToolbarActions(templateData: BaseCellRenderTemplate, disposables: DisposableStore): void { const updateActions = () => { - const actions = this.getCellToolbarActions(menu); + const actions = this.getCellToolbarActions(templateData.titleMenu); const hadFocus = DOM.isAncestor(document.activeElement, templateData.toolbar.getContainer()); templateData.toolbar.setActions(actions.primary, actions.secondary); @@ -288,7 +287,7 @@ abstract class AbstractCellRenderer { }; updateActions(); - disposables.add(menu.onDidChange(() => { + disposables.add(templateData.titleMenu.onDidChange(() => { if (this.notebookEditor.isDisposed) { return; } @@ -357,6 +356,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR DOM.append(bottomCellContainer, $('.separator')); const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); + const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: MarkdownCellRenderTemplate = { contextKeyService, @@ -373,6 +373,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR bottomCellContainer, statusBarContainer: statusBar.statusBarContainer, languageStatusBarItem: statusBar.languageStatusBarItem, + titleMenu, toJSON: () => { return {}; } }; this.dndController.registerDragHandle(templateData, () => this.getDragImage(templateData)); @@ -427,7 +428,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor.viewModel?.notebookDocument!, element)); // render toolbar first - this.setupCellToolbarActions(templateData.contextKeyService, templateData, elementDisposables); + this.setupCellToolbarActions(templateData, elementDisposables); const toolbarContext = { cell: element, @@ -917,6 +918,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const focusIndicatorBottom = DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom')); + const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); + const templateData: CodeCellRenderTemplate = { contextKeyService, container, @@ -941,6 +944,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende elementDisposables: new DisposableStore(), bottomCellContainer, timer, + titleMenu, toJSON: () => { return {}; } }; @@ -1091,7 +1095,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.updateForOutputs(element, templateData); elementDisposables.add(element.onDidChangeOutputs(_e => this.updateForOutputs(element, templateData))); - this.setupCellToolbarActions(templateData.contextKeyService, templateData, elementDisposables); + this.setupCellToolbarActions(templateData, elementDisposables); const toolbarContext = { cell: element, 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 e5485a272e..581f11a986 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 32341acadd..630a77923f 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 38daae91c7..1557de97cf 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -9,14 +9,14 @@ 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'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; export class CodeCellViewModel extends BaseCellViewModel implements ICellViewModel { - cellKind: CellKind.Code = CellKind.Code; + readonly cellKind = CellKind.Code; protected readonly _onDidChangeOutputs = new Emitter(); readonly onDidChangeOutputs = this._onDidChangeOutputs.event; private _outputCollection: number[] = []; @@ -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/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index 2e0713003d..5814924469 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -18,7 +18,7 @@ import { CellKind, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/ import { NotebookEventDispatcher, NotebookCellStateChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; export class MarkdownCellViewModel extends BaseCellViewModel implements ICellViewModel { - cellKind: CellKind.Markdown = CellKind.Markdown; + readonly cellKind = CellKind.Markdown; private _html: HTMLElement | null = null; private _layoutInfo: MarkdownCellLayoutInfo; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 6875e4a53a..3dfa4a5063 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 Source EULA. 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 45235dcc5f..93ae922efa 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 Source EULA. 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,21 +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(), - 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]); @@ -265,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); } @@ -334,6 +349,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel handle: cell.handle, uri: cell.uri, source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs, @@ -375,6 +391,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel handle: cell.handle, uri: cell.uri, source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs, diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d120782d0e..cf8ad4d5e1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -14,10 +14,10 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { GlobPattern } from 'vs/workbench/api/common/extHost.protocol'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { IRevertOptions } from 'vs/workbench/common/editor'; +import { basename } from 'vs/base/common/path'; export enum CellKind { Markdown = 1, @@ -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 { @@ -295,6 +304,7 @@ export interface IMainCellDto { handle: number; uri: UriComponents, source: string[]; + eol: string; language: string; cellKind: CellKind; outputs: IProcessedOutput[]; @@ -607,3 +617,57 @@ export interface INotebookSearchOptions { caseSensitive?: boolean wordSeparators?: string; } + +export interface INotebookDocumentFilter { + viewType?: string; + filenamePattern?: string | glob.IRelativePattern; + excludeFileNamePattern?: string | glob.IRelativePattern; +} + +//TODO@rebornix test +export function notebookDocumentFilterMatch(filter: INotebookDocumentFilter, viewType: string, resource: URI): boolean { + if (filter.viewType === viewType) { + return true; + } + + if (filter.filenamePattern) { + if (glob.match(filter.filenamePattern, basename(resource.fsPath).toLowerCase())) { + if (filter.excludeFileNamePattern) { + if (glob.match(filter.excludeFileNamePattern, basename(resource.fsPath).toLowerCase())) { + // should exclude + + return false; + } + } + return true; + } + } + return false; +} + +export interface INotebookKernelInfoDto2 { + id: string; + label: string; + extension: ExtensionIdentifier; + extensionLocation: URI; + providerHandle?: number; + description?: string; + isPreferred?: boolean; + preloads?: UriComponents[]; +} + +export interface INotebookKernelInfo2 extends INotebookKernelInfoDto2 { + resolve(uri: URI, editorId: string, 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): Promise; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index b8defa8820..8106991bc6 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 07d0152b9d..e3f39adbd1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -18,6 +18,8 @@ export interface NotebookEditorDescriptor { readonly displayName: string; readonly selector: readonly NotebookSelector[]; readonly priority: NotebookEditorPriority; + readonly providerExtensionId?: string; + readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; kernel?: INotebookKernelInfoDto; @@ -29,6 +31,9 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { readonly displayName: string; readonly selector: readonly NotebookSelector[]; readonly priority: NotebookEditorPriority; + // it's optional as the memento might not have it + readonly providerExtensionId?: string; + readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; kernel?: INotebookKernelInfoDto; @@ -38,6 +43,8 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { this.displayName = descriptor.displayName; this.selector = descriptor.selector; this.priority = descriptor.priority; + 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 91bcca6655..cbd95a5737 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,22 +8,30 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, ICellEditOperation, NotebookCellOutputsSplice, IOrderedMimeType, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { + 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'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const INotebookService = createDecorator('notebookService'); 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; - executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, 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, useAttachedKernel: boolean, 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; @@ -39,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; @@ -48,14 +57,18 @@ export interface INotebookService { transformSingleOutput(textModel: NotebookTextModel, output: IProcessedOutput, rendererId: string, mimeType: string): Promise; registerNotebookKernel(kernel: INotebookKernelInfo): void; unregisterNotebookKernel(id: string): void; + registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; getContributedNotebookKernels(viewType: string, resource: URI): readonly INotebookKernelInfo[]; + 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, useAttachedKernel: boolean, token: CancellationToken): Promise; - executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, 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 fb1c353091..1a0fc4ba62 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/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 335bbdd0ec..06ecdd65f4 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -62,6 +62,8 @@ export class TestNotebookEditor implements INotebookEditor { constructor( ) { } + multipleKernelsAvailable: boolean = false; + uri?: URI | undefined; textModel?: NotebookTextModel | undefined; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 213d5a6935..ed6f769ee7 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -18,8 +18,8 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as nls from 'vs/nls'; -import { ConfigurationTarget, IConfigurationService, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationNode, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationNode, OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 3527d5f77a..19c5eabbd3 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1570,7 +1570,6 @@ export class SettingTreeRenderers { const settingRenderers = [ this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory), - this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingArrayRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingTextRenderer, this.settingActions, actionFactory), diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 37d050b7d3..4489d61964 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -23,6 +23,7 @@ import { IEditorInput } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -153,6 +154,7 @@ export class PreferencesContribution implements IWorkbenchContribution { const registry = Registry.as(Extensions.Configuration); registry.registerConfiguration({ + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.settings.enableNaturalLanguageSearch': { 'type': 'boolean', diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 78b5ecb3d6..1193c7d30a 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/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 195c07c2df..42e2b677aa 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { basename, relativePath } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEW_PANE_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -182,7 +182,7 @@ export class SCMStatusController implements IWorkbenchContribution { if (count > 0) { const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge, clazz: 'scm-viewlet-label' }); + this.badgeDisposable.value = this.activityService.showViewActivity(VIEW_PANE_ID, { badge, clazz: 'scm-viewlet-label' }); } else { this.badgeDisposable.clear(); } diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 84d7e98a7a..4487ae8fd9 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -36,9 +36,9 @@ } .scm-view .scm-provider > .label { - display: flex; - flex-shrink: 1; + flex: 1; overflow: hidden; + text-overflow: ellipsis; } .scm-view .scm-provider > .label > .name { @@ -52,8 +52,6 @@ } .scm-view .scm-provider > .actions { - flex: 1; - padding-left: 10px; overflow: hidden; justify-content: flex-end; } @@ -81,6 +79,11 @@ background-position: center; } +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label > .codicon { + font-size: 12px; + padding-right: 2px; +} + .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:last-of-type { padding-right: 0; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index ff895e27ba..0008c951dc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -152,6 +152,7 @@ interface ISCMLayout { } interface RepositoryTemplate { + readonly label: HTMLElement; readonly name: HTMLElement; readonly description: HTMLElement; readonly countContainer: HTMLElement; @@ -192,7 +193,7 @@ class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, templateData: RepositoryTemplate, height: number | undefined): void { @@ -202,9 +203,11 @@ class RepositoryRenderer implements ICompressibleTreeRenderer ({ element, incompressible: true })) diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index e941b17ac7..0bf0770781 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 4a3969dc0b..b733a19a0e 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); } @@ -1584,7 +1587,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private isTaskProviderEnabled(type: string) { const definition = TaskDefinitionRegistry.get(type); - return !definition.when || this.contextKeyService.contextMatchesRules(definition.when); + return !definition || !definition.when || this.contextKeyService.contextMatchesRules(definition.when); } private getGroupedTasks(type?: string): Promise { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 0b0d1f2feb..e5d058c0bc 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1022,7 +1022,7 @@ export class TerminalTaskSystem implements ITaskSystem { } else { let commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined; let executable = !isShellCommand - ? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}') + ? this.resolveVariable(variableResolver, this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}')) : commandExecutable; // When we have a process task there is no need to quote arguments. So we go ahead and take the string value. diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index f39c98ae4e..005c364ab1 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 5af4a32e0a..673ac1b6dc 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -55,7 +55,7 @@ export class OpenLatestReleaseNotesInBrowserAction extends Action { if (this.productService.releaseNotesUrl) { const uri = URI.parse(this.productService.releaseNotesUrl); await this.openerService.open(uri); - } else { //{{SQL CARBON EDIT}} + } else { throw new Error(nls.localize('update.noReleaseNotesOnline', "This version of {0} does not have release notes online", this.productService.nameLong)); } } @@ -507,7 +507,9 @@ export class SwitchProductQualityContribution extends Disposable implements IWor const res = await dialogService.confirm({ type: 'info', message: nls.localize('relaunchMessage', "Changing the version requires a reload to take effect"), - detail: newQuality === 'insider' ? nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the insiders version.") : nls.localize('relaunchDetailStable', "Press the reload button to switch to the stable version."), + detail: newQuality === 'insider' ? + nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the nightly pre-production version of VSCode.") : + nls.localize('relaunchDetailStable', "Press the reload button to switch to the monthly released stable version of VSCode."), primaryButton: nls.localize('reload', "&&Reload") }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/userDataSyncViews.css b/src/vs/workbench/contrib/userDataSync/browser/media/userDataSyncViews.css new file mode 100644 index 0000000000..b7d003d3d6 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/media/userDataSyncViews.css @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .pane > .pane-body > .manual-sync-buttons-container { + display: flex; + flex-direction: column; + padding: 13px 20px 0 20px; + box-sizing: border-box; +} + +.monaco-workbench .pane > .pane-body > .manual-sync-buttons-container .monaco-button { + margin-block-start: 13px; + margin-inline-start: 0px; + margin-inline-end: 0px; + max-width: 260px; + margin-left: auto; + margin-right: auto; +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts index d50afa4771..e6b505b73b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts @@ -3,174 +3,160 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer, ITreeView } from 'vs/workbench/common/views'; +import 'vs/css!./media/userDataSyncViews'; +import { ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, IViewDescriptorService } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IUserDataSyncService, Change } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, Change, MergeState, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; +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 { FileThemeIcon } from 'vs/platform/theme/common/themeService'; -import { Event, 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, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, IUserDataSyncPreview, IUserDataSyncResourceGroup, MANUAL_SYNC_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; -import { TreeView } from 'vs/workbench/contrib/views/browser/treeView'; +import { IUserDataSyncWorkbenchService, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, MANUAL_SYNC_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { isEqual, basename } from 'vs/base/common/resources'; import { IDecorationsProvider, IDecorationData, IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; import { IProgressService } from 'vs/platform/progress/common/progress'; +import { listWarningForeground, listDeemphasizedForeground } from 'vs/platform/theme/common/colorRegistry'; +import * as DOM from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +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'; -const viewName = localize('manual sync', "Manual Sync"); +export class UserDataManualSyncViewPane extends TreeViewPane { -export class UserDataManualSyncView extends Disposable { - - private readonly treeView: ITreeView; private userDataSyncPreview: IUserDataSyncPreview; + private buttonsContainer!: HTMLElement; + private syncButton!: Button; + private cancelButton!: Button; + + private readonly treeItems = new Map(); + constructor( - container: ViewContainer, - @IInstantiationService private readonly instantiationService: IInstantiationService, + 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, @IDecorationsService decorationsService: IDecorationsService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(); - + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.userDataSyncPreview = userDataSyncWorkbenchService.userDataSyncPreview; - this.treeView = this.createTreeView(); - this.registerManualSyncView(container); + + this._register(this.userDataSyncPreview.onDidChangeResources(() => this.updateSyncButtonEnablement())); + this._register(this.userDataSyncPreview.onDidChangeResources(() => this.treeView.refresh())); + this._register(this.userDataSyncPreview.onDidChangeResources(() => this.closeConflictEditors())); + this._register(decorationsService.registerDecorationsProvider(this._register(new UserDataSyncResourcesDecorationProvider(this.userDataSyncPreview)))); + this.registerActions(); - - decorationsService.registerDecorationsProvider(this._register(new UserDataSyncResourcesDecorationProvider(this.userDataSyncPreview))); } - private createTreeView(): ITreeView { - const treeView = this.instantiationService.createInstance(TreeView, MANUAL_SYNC_VIEW_ID, viewName); + protected renderTreeView(container: HTMLElement): void { + super.renderTreeView(DOM.append(container, DOM.$(''))); + this.createButtons(container); - this._register(Event.any( - this.userDataSyncPreview.onDidChangeChanges, - this.userDataSyncPreview.onDidChangeConflicts - )(() => treeView.refresh())); + 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(); } }; + } - const disposable = treeView.onDidChangeVisibility(visible => { - if (visible && !treeView.dataProvider) { - disposable.dispose(); - treeView.dataProvider = new ManualSyncViewDataProvider(this.userDataSyncPreview); + private createButtons(container: HTMLElement): void { + this.buttonsContainer = DOM.append(container, DOM.$('.manual-sync-buttons-container')); + + this.syncButton = this._register(new Button(this.buttonsContainer)); + this.syncButton.label = localize('turn on sync', "Turn on Preferences Sync"); + this.updateSyncButtonEnablement(); + this._register(attachButtonStyler(this.syncButton, this.themeService)); + this._register(this.syncButton.onDidClick(() => this.apply())); + + this.cancelButton = this._register(new Button(this.buttonsContainer, { secondary: true })); + this.cancelButton.label = localize('cancel', "Cancel"); + this._register(attachButtonStyler(this.cancelButton, this.themeService)); + this._register(this.cancelButton.onDidClick(() => this.cancel())); + } + + protected layoutTreeView(height: number, width: number): void { + 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; + 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 treeView; + } + return roots; } - private registerManualSyncView(container: ViewContainer): void { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViews([{ - id: MANUAL_SYNC_VIEW_ID, - name: viewName, - ctorDescriptor: new SyncDescriptor(TreeViewPane), - when: CONTEXT_ENABLE_MANUAL_SYNC_VIEW, - canToggleVisibility: false, - canMoveView: false, - treeView: this.treeView, - collapsed: false, - order: 100, - }], container); + 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 localActionOrder = 1; - const remoteActionOrder = 1; - const mergeActionOrder = 1; const that = this; - /* accept all local */ - registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.sync.acceptLocalAll`, - title: localize('workbench.actions.sync.acceptLocalAll', "Accept Local"), - icon: Codicon.cloudUpload, - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID)), - group: 'navigation', - order: localActionOrder, - }, - }); - } - run(accessor: ServicesAccessor): Promise { - return that.push(); - } - }); - - /* accept all remote */ - registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.sync.acceptRemoteAll`, - title: localize('workbench.actions.sync.acceptRemoteAll', "Accept Remote"), - icon: Codicon.cloudDownload, - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID)), - group: 'navigation', - order: remoteActionOrder, - }, - }); - } - async run(accessor: ServicesAccessor): Promise { - return that.pull(); - } - }); - - /* merge all */ - registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.sync.mergeAll`, - title: localize('workbench.actions.sync.mergeAll', "Merge"), - icon: Codicon.sync, - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID)), - group: 'navigation', - order: mergeActionOrder, - }, - }); - } - async run(accessor: ServicesAccessor): Promise { - return that.merge(); - } - }); - - /* accept local change */ - registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.sync.acceptLocal`, - title: localize('workbench.actions.sync.acceptLocal', "Accept Local"), - icon: Codicon.cloudUpload, - menu: { - id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-modified-.*/i)), - group: 'inline', - order: localActionOrder, - }, - }); - } - async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.acceptLocal(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); - } - }); - /* accept remote change */ - registerAction2(class extends Action2 { + this._register(registerAction2(class extends Action2 { constructor() { super({ id: `workbench.actions.sync.acceptRemote`, @@ -178,76 +164,78 @@ export class UserDataManualSyncView extends Disposable { icon: Codicon.cloudDownload, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-modified-.*/i)), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), group: 'inline', - order: remoteActionOrder, + order: 1, }, }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.acceptRemote(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.acceptRemote(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } - }); + })); + + /* accept local change */ + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.acceptLocal`, + title: localize('workbench.actions.sync.acceptLocal', "Accept Local"), + icon: Codicon.cloudUpload, + menu: { + id: MenuId.ViewItemContext, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), + group: 'inline', + order: 2, + }, + }); + } + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + return that.acceptLocal(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + } + })); /* merge */ - registerAction2(class extends Action2 { + this._register(registerAction2(class extends Action2 { constructor() { super({ id: `workbench.actions.sync.merge`, title: localize('workbench.actions.sync.merge', "Merge"), - icon: Codicon.sync, + icon: Codicon.merge, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-modified-change')), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), group: 'inline', - order: mergeActionOrder, + order: 3, }, }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.mergeResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.mergeResource(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } - }); + })); - /* delete */ - registerAction2(class extends Action2 { + /* discard */ + this._register(registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.sync.deleteLocal`, - title: localize('workbench.actions.sync.deleteLocal', "Delete"), - icon: Codicon.trash, + id: `workbench.actions.sync.undo`, + title: localize('workbench.actions.sync.discard', "Discard"), + icon: Codicon.discard, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-(add|delete)-.*/i)), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.or(ContextKeyExpr.equals('viewItem', 'sync-resource-accepted'), ContextKeyExpr.equals('viewItem', 'sync-resource-conflict'))), group: 'inline', + order: 3, }, }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.deleteResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); + return that.discardResource(that.toUserDataSyncResourceGroup(handle.$treeItemHandle)); } - }); + })); - /* add */ - registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.actions.sync.addLocal`, - title: localize('workbench.actions.sync.addLocal', "Add"), - icon: Codicon.add, - menu: { - id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-(add|delete)-.*/i)), - group: 'inline', - }, - }); - } - async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - return that.addResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle)); - } - }); - - registerAction2(class extends Action2 { + this._register(registerAction2(class extends Action2 { constructor() { super({ id: `workbench.actions.sync.showChanges`, @@ -255,69 +243,76 @@ export class UserDataManualSyncView extends Disposable { }); } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { - const previewResource: IUserDataSyncResourceGroup = ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle); - return that.showChanges(previewResource); + const previewResource: IUserDataSyncResource = that.toUserDataSyncResourceGroup(handle.$treeItemHandle); + return that.open(previewResource); } + })); + } + + 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.reopen(userDataSyncResource); } - private async push(): Promise { - return this.withProgress(() => this.userDataSyncPreview.push()); - } - - private async pull(): Promise { - return this.withProgress(() => this.userDataSyncPreview.pull()); - } - - private async merge(): Promise { - return this.withProgress(() => this.userDataSyncPreview.merge()); - } - - private async acceptLocal(previewResource: IUserDataSyncResourceGroup): Promise { - const isConflict = this.userDataSyncPreview.conflicts.some(({ local }) => isEqual(local, previewResource.local)); - const localResource = isConflict ? previewResource.preview : previewResource.local; - return this.withProgress(async () => { - const content = await this.userDataSyncService.resolveContent(localResource); - await this.userDataSyncPreview.accept(previewResource.syncResource, localResource, content || ''); + 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.reopen(userDataSyncResource); } - private async acceptRemote(previewResource: IUserDataSyncResourceGroup): Promise { - return this.withProgress(async () => { - const content = await this.userDataSyncService.resolveContent(previewResource.remote); - await this.userDataSyncPreview.accept(previewResource.syncResource, previewResource.remote, content || ''); - }); + 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 mergeResource(previewResource: IUserDataSyncResourceGroup): Promise { - return this.withProgress(() => this.userDataSyncPreview.merge(previewResource.preview)); + private async discardResource(previewResource: IUserDataSyncResource): Promise { + this.close(previewResource); + return this.withProgress(() => this.userDataSyncPreview.discard(previewResource.merged)); } - private async deleteResource(previewResource: IUserDataSyncResourceGroup): Promise { - const resource = previewResource.remoteChange === Change.Deleted || previewResource.localChange === Change.Added ? previewResource.local : previewResource.remote; - return this.withProgress(async () => { - const content = await this.userDataSyncService.resolveContent(resource); - await this.userDataSyncPreview.accept(previewResource.syncResource, resource, content || ''); - }); + private async apply(): Promise { + this.closeAll(); + this.syncButton.label = localize('turning on', "Turning on..."); + this.syncButton.enabled = false; + this.cancelButton.enabled = false; + try { + await this.withProgress(async () => this.userDataSyncPreview.apply()); + } catch (error) { + this.syncButton.enabled = false; + this.cancelButton.enabled = true; + } } - private async addResource(previewResource: IUserDataSyncResourceGroup): Promise { - const resource = previewResource.remoteChange === Change.Added || previewResource.localChange === Change.Deleted ? previewResource.local : previewResource.remote; - return this.withProgress(async () => { - const content = await this.userDataSyncService.resolveContent(resource); - await this.userDataSyncPreview.accept(previewResource.syncResource, resource, content || ''); - }); + private async cancel(): Promise { + for (const resource of this.userDataSyncPreview.resources) { + this.close(resource); + } + await this.userDataSyncPreview.cancel(); } - private async showChanges(previewResource: IUserDataSyncResourceGroup): Promise { - const isConflict = this.userDataSyncPreview.conflicts.some(({ local }) => isEqual(local, previewResource.local)); - if (previewResource.localChange === Change.Added || previewResource.remoteChange === Change.Deleted) { - await this.editorService.openEditor({ resource: URI.revive(previewResource.remote), label: localize({ key: 'resourceLabel', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(previewResource.remote)) }); + private async open(previewResource: IUserDataSyncResource): Promise { + if (previewResource.mergeState === MergeState.Accepted) { + if (previewResource.localChange !== Change.Deleted && previewResource.remoteChange !== Change.Deleted) { + // Do not open deleted preview + await this.editorService.openEditor({ resource: previewResource.accepted, label: localize('preview', "{0} (Preview)", basename(previewResource.accepted)) }); + } } else { - const leftResource = URI.revive(previewResource.remote); - const rightResource = isConflict ? URI.revive(previewResource.preview) : URI.revive(previewResource.local); + 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 = localize({ key: 'rightResourceName', comment: ['local as in file in disk'] }, "{0} (Local)", 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, rightResource, @@ -330,91 +325,55 @@ export class UserDataManualSyncView extends Disposable { } } - private withProgress(task: () => Promise): Promise { - return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task); + private async reopen(previewResource: IUserDataSyncResource): Promise { + 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); + } } -} - -class ManualSyncViewDataProvider implements ITreeViewDataProvider { - - constructor( - private readonly userDataSyncPreview: IUserDataSyncPreview - ) { - } - - async getChildren(element?: ITreeItem): Promise { - if (element) { - if (element.handle === 'changes') { - return this.getChanges(); - } else { - return this.getConflicts(); + private close(previewResource: IUserDataSyncResource): void { + for (const input of this.editorService.editors) { + if (input instanceof DiffEditorInput) { + // Close all diff editors + if (isEqual(previewResource.remote, input.secondary.resource)) { + input.dispose(); + } + } + // Close all preview editors + else if (isEqual(previewResource.accepted, input.resource)) { + input.dispose(); } } - return this.getRoots(); } - private getRoots(): ITreeItem[] { - const roots: ITreeItem[] = []; - if (this.userDataSyncPreview.changes.length) { - roots.push({ - handle: 'changes', - collapsibleState: TreeItemCollapsibleState.Expanded, - label: { label: localize('changes', "Changes") }, - themeIcon: Codicon.folder, - contextValue: 'changes' - }); + private closeConflictEditors() { + for (const previewResource of this.userDataSyncPreview.resources) { + if (previewResource.mergeState !== MergeState.Conflict) { + for (const input of this.editorService.editors) { + if (input instanceof DiffEditorInput) { + if (isEqual(previewResource.remote, input.secondary.resource) && isEqual(previewResource.merged, input.primary.resource)) { + input.dispose(); + } + } + } + } } - if (this.userDataSyncPreview.conflicts.length) { - roots.push({ - handle: 'conflicts', - collapsibleState: TreeItemCollapsibleState.Expanded, - label: { label: localize('conflicts', "Conflicts") }, - themeIcon: Codicon.folder, - contextValue: 'conflicts', - }); + } + + private closeAll() { + for (const previewResource of this.userDataSyncPreview.resources) { + this.close(previewResource); } - return roots; } - private getChanges(): ITreeItem[] { - return this.userDataSyncPreview.changes.map(change => { - return { - handle: JSON.stringify(change), - resourceUri: change.remote, - themeIcon: FileThemeIcon, - description: getSyncAreaLabel(change.syncResource), - contextValue: `sync-resource-${change.localChange === Change.Added ? 'add-local' : change.localChange === Change.Deleted ? 'delete-local' : change.remoteChange === Change.Added ? 'add-remote' : change.remoteChange === Change.Deleted ? 'delete-remote' : 'modified'}-change`, - collapsibleState: TreeItemCollapsibleState.None, - command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: JSON.stringify(change) }] }, - }; - }); - } - - private getConflicts(): ITreeItem[] { - return this.userDataSyncPreview.conflicts.map(conflict => { - return { - handle: JSON.stringify(conflict), - resourceUri: conflict.remote, - themeIcon: FileThemeIcon, - description: getSyncAreaLabel(conflict.syncResource), - contextValue: `sync-resource-modified-conflict`, - collapsibleState: TreeItemCollapsibleState.None, - command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: JSON.stringify(conflict) }] }, - }; - }); - } - - static toUserDataSyncResourceGroup(handle: string): IUserDataSyncResourceGroup { - const parsed: IUserDataSyncResourceGroup = JSON.parse(handle); - return { - syncResource: parsed.syncResource, - local: URI.revive(parsed.local), - preview: URI.revive(parsed.preview), - remote: URI.revive(parsed.remote), - localChange: parsed.localChange, - remoteChange: parsed.remoteChange - }; + private withProgress(task: () => Promise): Promise { + return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task); } } @@ -428,25 +387,118 @@ class UserDataSyncResourcesDecorationProvider extends Disposable implements IDec constructor(private readonly userDataSyncPreview: IUserDataSyncPreview) { super(); + this._register(userDataSyncPreview.onDidChangeResources(c => this._onDidChange.fire(c.map(({ remote }) => remote)))); } provideDecorations(resource: URI): IDecorationData | undefined { - const changeResource = this.userDataSyncPreview.changes.find(c => isEqual(c.remote, resource)) || this.userDataSyncPreview.conflicts.find(c => isEqual(c.remote, resource)); - if (changeResource) { - if (changeResource.localChange === Change.Modified || changeResource.remoteChange === Change.Modified) { - return { - letter: 'M', - }; - } - if (changeResource.localChange === Change.Added - || changeResource.localChange === Change.Deleted - || changeResource.remoteChange === Change.Added - || changeResource.remoteChange === Change.Deleted) { - return { - letter: 'A', - }; + const userDataSyncResource = this.userDataSyncPreview.resources.find(c => isEqual(c.remote, resource)); + if (userDataSyncResource) { + switch (userDataSyncResource.mergeState) { + case MergeState.Conflict: + return { letter: '⚠', color: listWarningForeground, tooltip: localize('conflict', "Conflicts Detected") }; + case MergeState.Accepted: + return { letter: '✓', color: listDeemphasizedForeground, tooltip: localize('accepted', "Accepted") }; } } 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 e21ca8efa9..bc0be89dab 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))); @@ -153,6 +154,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private readonly conflictsDisposables = new Map(); private onDidChangeConflicts(conflicts: [SyncResource, IResourcePreview[]][]) { + if (!this.userDataAutoSyncService.isEnabled()) { + return; + } this.updateGlobalActivityBadge(); if (conflicts.length) { const conflictsSources: SyncResource[] = conflicts.map(([syncResource]) => syncResource); @@ -194,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]); @@ -240,7 +244,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo for (const conflict of conflicts) { const modelRef = await this.textModelResolverService.createModelReference(conflict.remoteResource); try { - await this.userDataSyncService.acceptPreviewContent(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue()); + await this.userDataSyncService.accept(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled()); } finally { modelRef.dispose(); } @@ -255,7 +259,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo for (const conflict of conflicts) { const modelRef = await this.textModelResolverService.createModelReference(conflict.previewResource); try { - await this.userDataSyncService.acceptPreviewContent(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue()); + await this.userDataSyncService.accept(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled()); } finally { modelRef.dispose(); } @@ -305,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()) ] } @@ -386,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...")); @@ -461,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()) ] } @@ -483,8 +487,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo Severity.Info, localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."), [ - localize('open doc', "Open Documentation"), localize('turn on', "Turn On"), + localize('open doc', "Open Documentation"), localize('cancel', "Cancel"), ], { @@ -492,7 +496,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } ); switch (result.choice) { - case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return false; + case 1: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return false; case 2: return false; } return true; @@ -647,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, @@ -1125,7 +1123,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, ) { super(); @@ -1154,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; @@ -1176,8 +1179,8 @@ 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 local', "Accept Local"); - 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) { @@ -1187,15 +1190,15 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio type: 'info', title: isRemote ? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel) - : localize('Sync accept local', "Preferences Sync: {0}", acceptLocalLabel), + : 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 local {0} and replace remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), - primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel + : 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.acceptPreviewContent(syncResource, model.uri, model.getValue()); + 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]; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 1d86002ede..70679fe701 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -30,13 +30,13 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IAction, Action } from 'vs/base/common/actions'; -import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_ACTIVITY_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_ACTIVITY_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_MANUAL_SYNC_VIEW } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TreeView } from 'vs/workbench/contrib/views/browser/treeView'; import { flatten } from 'vs/base/common/arrays'; -import { UserDataManualSyncView } from 'vs/workbench/contrib/userDataSync/browser/userDataManualSyncView'; +import { UserDataManualSyncViewPane } from 'vs/workbench/contrib/userDataSync/browser/userDataManualSyncView'; export class UserDataSyncViewPaneContainer extends ViewPaneContainer { @@ -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()), ]; } @@ -86,7 +86,7 @@ export class UserDataSyncDataViews extends Disposable { } private registerViews(container: ViewContainer): void { - this._register(this.instantiationService.createInstance(UserDataManualSyncView, container)); + this.registerManualSyncView(container); this.registerActivityView(container, true); this.registerMachinesView(container); @@ -94,6 +94,22 @@ export class UserDataSyncDataViews extends Disposable { this.registerActivityView(container, false); } + private registerManualSyncView(container: ViewContainer): void { + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + const viewName = localize('manual sync', "Manual Sync"); + viewsRegistry.registerViews([{ + id: MANUAL_SYNC_VIEW_ID, + name: viewName, + ctorDescriptor: new SyncDescriptor(UserDataManualSyncViewPane), + when: CONTEXT_ENABLE_MANUAL_SYNC_VIEW, + canToggleVisibility: false, + canMoveView: false, + treeView: this.instantiationService.createInstance(TreeView, MANUAL_SYNC_VIEW_ID, viewName), + collapsed: false, + order: 100, + }], container); + } + private registerMachinesView(container: ViewContainer): void { const id = `workbench.views.sync.machines`; const name = localize('synced machines', "Synced Machines"); diff --git a/src/vs/workbench/contrib/views/browser/treeView.ts b/src/vs/workbench/contrib/views/browser/treeView.ts index a06b7ab77e..9616248388 100644 --- a/src/vs/workbench/contrib/views/browser/treeView.ts +++ b/src/vs/workbench/contrib/views/browser/treeView.ts @@ -755,9 +755,23 @@ class TreeRenderer extends Disposable implements ITreeRenderer('explorer.decorations'); - templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); + templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { + fileKind: this.getFileKind(node), + title, + hideIcon: !!iconUrl, + fileDecorations, + extraClasses: ['custom-view-tree-node-item-resourceLabel'], + matches: matches ? matches : createMatches(element.filterData), + strikethrough: treeItemLabel?.strikethrough, + }); } else { - templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); + templateData.resourceLabel.setResource({ name: label, description }, { + title, + hideIcon: true, + extraClasses: ['custom-view-tree-node-item-resourceLabel'], + matches: matches ? matches : createMatches(element.filterData), + strikethrough: treeItemLabel?.strikethrough, + }); } templateData.icon.title = title ? title : ''; @@ -788,7 +802,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { @@ -138,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); } } ], @@ -167,9 +169,10 @@ export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut { @IConfigurationService configurationService: IConfigurationService, @IExtensionGalleryService galleryService: IExtensionGalleryService, @IProductService productService: IProductService, - @IEnvironmentService environmentService: IEnvironmentService // {{SQL CARBON EDIT}} add service + @IEnvironmentService environmentService: IEnvironmentService, + @IJSONEditingService jsonEditingService: IJSONEditingService ) { - super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService); + 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 d54d95643e..c15901d333 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,7 @@ 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 { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -28,10 +29,11 @@ export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { @IConfigurationService configurationService: IConfigurationService, @IExtensionGalleryService galleryService: IExtensionGalleryService, @IProductService productService: IProductService, - @IEnvironmentService environmentService: IEnvironmentService, // {{SQL CARBON EDIT}} add service - @IElectronService private readonly electronService: IElectronService, + @IEnvironmentService environmentService: IEnvironmentService, + @IJSONEditingService jsonEditingService: IJSONEditingService, + @IElectronService private readonly electronService: IElectronService ) { - super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService); + 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 9acb83a07f..d98154b2b9 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 5e03711455..69ae27fdcd 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -357,6 +357,14 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten 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/activity/browser/activityService.ts b/src/vs/workbench/services/activity/browser/activityService.ts index 154ef13aca..d81ff9c48a 100644 --- a/src/vs/workbench/services/activity/browser/activityService.ts +++ b/src/vs/workbench/services/activity/browser/activityService.ts @@ -5,20 +5,68 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IActivityService, IActivity } from 'vs/workbench/services/activity/common/activity'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { GLOBAL_ACTIVITY_ID, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity'; +import { Event } from 'vs/base/common/event'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +class ViewContainerActivityByView extends Disposable { + + private activity: IActivity | undefined = undefined; + private activityDisposable: IDisposable = Disposable.None; + + constructor( + private readonly viewId: string, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IActivityService private readonly activityService: IActivityService, + ) { + super(); + this._register(Event.filter(this.viewDescriptorService.onDidChangeContainer, e => e.views.some(view => view.id === viewId))(() => this.update())); + this._register(Event.filter(this.viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => this.update())); + } + + setActivity(activity: IActivity): void { + this.activity = activity; + this.update(); + } + + clearActivity(): void { + this.activity = undefined; + this.update(); + } + + private update(): void { + this.activityDisposable.dispose(); + const container = this.viewDescriptorService.getViewContainerByViewId(this.viewId); + if (container && this.activity) { + this.activityDisposable = this.activityService.showViewContainerActivity(container.id, this.activity); + } + } + + dispose() { + this.activityDisposable.dispose(); + } +} + +interface IViewActivity { + id: number; + readonly activity: ViewContainerActivityByView; +} export class ActivityService implements IActivityService { public _serviceBrand: undefined; + private viewActivities = new Map(); + constructor( @IPanelService private readonly panelService: IPanelService, @IActivityBarService private readonly activityBarService: IActivityBarService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { } showViewContainerActivity(viewContainerId: string, { badge, clazz, priority }: IActivity): IDisposable { @@ -35,6 +83,32 @@ export class ActivityService implements IActivityService { return Disposable.None; } + showViewActivity(viewId: string, activity: IActivity): IDisposable { + let maybeItem = this.viewActivities.get(viewId); + + if (maybeItem) { + maybeItem.id++; + } else { + maybeItem = { + id: 1, + activity: this.instantiationService.createInstance(ViewContainerActivityByView, viewId) + }; + + this.viewActivities.set(viewId, maybeItem); + } + + const id = maybeItem.id; + maybeItem.activity.setActivity(activity); + + const item = maybeItem; + return toDisposable(() => { + if (item.id === id) { + item.activity.dispose(); + this.viewActivities.delete(viewId); + } + }); + } + showAccountsActivity({ badge, clazz, priority }: IActivity): IDisposable { return this.activityBarService.showActivity(ACCOUNTS_ACTIIVTY_ID, badge, clazz, priority); } diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index 275426336d..7595f846f0 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -3,10 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { Event } from 'vs/base/common/event'; export interface IActivity { readonly badge: IBadge; @@ -25,6 +23,11 @@ export interface IActivityService { */ showViewContainerActivity(viewContainerId: string, badge: IActivity): IDisposable; + /** + * Show activity for the given view + */ + showViewActivity(viewId: string, badge: IActivity): IDisposable; + /** * Show accounts activity */ @@ -36,39 +39,6 @@ export interface IActivityService { showGlobalActivity(activity: IActivity): IDisposable; } -export class ViewContaierActivityByView extends Disposable { - - private activity: IActivity | undefined = undefined; - private activityDisposable: IDisposable = Disposable.None; - - constructor( - private readonly viewId: string, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IActivityService private readonly activityService: IActivityService, - ) { - super(); - this._register(Event.filter(this.viewDescriptorService.onDidChangeContainer, e => e.views.some(view => view.id === viewId))(() => this.update())); - this._register(Event.filter(this.viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => this.update())); - } - - setActivity(activity: IActivity): void { - this.activity = activity; - this.update(); - } - - private update(): void { - this.activityDisposable.dispose(); - const container = this.viewDescriptorService.getViewContainerByViewId(this.viewId); - if (container && this.activity) { - this.activityDisposable = this.activityService.showViewContainerActivity(container.id, this.activity); - } - } - - dispose() { - this.activityDisposable.dispose(); - } -} - export interface IBadge { getDescription(): string; } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index a0cd2f1583..8c8d3b6caf 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -94,7 +94,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic }); })); - this._register(Registry.as(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas())); + const configurationRegistry = Registry.as(Extensions.Configuration); + if (environmentService.options?.configurationDefaults) { + configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]); + } + this._register(configurationRegistry.onDidSchemaChange(e => this.registerConfigurationSchemas())); this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties))); this.workspaceEditingQueue = new Queue(); diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index c6f0b665b2..a4cc1bbeee 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { equals } from 'vs/base/common/objects'; -import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; +import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; -import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; export class WorkspaceConfigurationModelParser extends ConfigurationModelParser { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index c31f59e3be..a20f657ee3 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -25,7 +25,6 @@ import { coalesce } from 'vs/base/common/arrays'; import { trim } from 'vs/base/common/strings'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { isWindows } from 'vs/base/common/platform'; export abstract class AbstractFileDialogService implements IFileDialogService { @@ -259,7 +258,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { // Build the file filter by using our known languages const ext: string | undefined = defaultUri ? resources.extname(defaultUri) : undefined; let matchingFilter: IFilter | undefined; - const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { + const registeredLanguageFilters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { const extensions = this.modeService.getExtensions(languageName); if (!extensions || !extensions.length) { return null; @@ -279,24 +278,20 @@ export abstract class AbstractFileDialogService implements IFileDialogService { // We have no matching filter, e.g. because the language // is unknown. We still add the extension to the list of // filters though so that it can be picked - // (https://github.com/microsoft/vscode/issues/96283) but - // only on Windows where this is an issue. Adding this to - // macOS would result in the following bugs: - // https://github.com/microsoft/vscode/issues/100614 and - // https://github.com/microsoft/vscode/issues/100241 - if (isWindows && !matchingFilter && ext) { + // (https://github.com/microsoft/vscode/issues/96283) + if (!matchingFilter && ext) { matchingFilter = { name: trim(ext, '.').toUpperCase(), extensions: [trim(ext, '.')] }; } // Order of filters is - // - File Extension Match - // - All Files + // - All Files (we MUST do this to fix macOS issue https://github.com/microsoft/vscode/issues/102713) + // - File Extension Match (if any) // - All Languages // - No Extension options.filters = coalesce([ - matchingFilter, { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }, - ...filters, + matchingFilter, + ...registeredLanguageFilters, { name: nls.localize('noExt', "No Extension"), extensions: [''] } ]); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index d996a36b50..f2ca686c12 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -874,16 +874,21 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Derive the label from the path if not provided explicitly const label = resourceEditorInput.label || basename(resourceEditorInput.resource); + // We keep track of the preferred resource this input is to be created + // with but it may be different from the canonical resource (see below) + const preferredResource = resourceEditorInput.resource; + // From this moment on, only operate on the canonical resource // to ensure we reduce the chance of opening the same resource // with different resource forms (e.g. path casing on Windows) - const canonicalResource = this.asCanonicalEditorResource(resourceEditorInput.resource); + const canonicalResource = this.asCanonicalEditorResource(preferredResource); + return this.createOrGetCached(canonicalResource, () => { // File - if (resourceEditorInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(canonicalResource)) { - return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, resourceEditorInput.resource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService); + if (resourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) { + return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService); } // Resource @@ -897,7 +902,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Files else if (!(cachedInput instanceof ResourceEditorInput)) { - cachedInput.setLabel(resourceEditorInput.resource); + cachedInput.setPreferredResource(preferredResource); if (resourceEditorInput.encoding) { cachedInput.setPreferredEncoding(resourceEditorInput.encoding); diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 1380b31e1a..de380a0785 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 94e305aed4..e9a88ff727 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -18,7 +18,9 @@ import { asText, isSuccess, IRequestService } from 'vs/platform/request/common/r import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { groupByExtension, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStaticExtension } from 'vs/workbench/workbench.web.api'; interface IUserExtension { identifier: IExtensionIdentifier; @@ -46,27 +48,60 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService declare readonly _serviceBrand: undefined; - private readonly systemExtensionsPromise: Promise; - private readonly staticExtensions: IScannedExtension[]; - private readonly extensionsResource: URI | undefined; - private readonly userExtensionsResourceLimiter: Queue; + private readonly systemExtensionsPromise: Promise = Promise.resolve([]); + private readonly defaultExtensionsPromise: Promise = Promise.resolve([]); + private readonly extensionsResource: URI | undefined = undefined; + private readonly userExtensionsResourceLimiter: Queue = new Queue(); constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IBuiltinExtensionsScannerService private readonly builtinExtensionsScannerService: IBuiltinExtensionsScannerService, @IFileService private readonly fileService: IFileService, @IRequestService private readonly requestService: IRequestService, @ILogService private readonly logService: ILogService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { - this.extensionsResource = isWeb ? joinPath(environmentService.userRoamingDataHome, 'extensions.json') : undefined; - this.userExtensionsResourceLimiter = new Queue(); - this.systemExtensionsPromise = isWeb ? this.builtinExtensionsScannerService.scanBuiltinExtensions() : Promise.resolve([]); - const staticExtensions = environmentService.options && Array.isArray(environmentService.options.staticExtensions) ? environmentService.options.staticExtensions : []; - this.staticExtensions = staticExtensions.map(data => { - location: data.extensionLocation, + if (isWeb) { + this.extensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json'); + this.systemExtensionsPromise = this.builtinExtensionsScannerService.scanBuiltinExtensions(); + this.defaultExtensionsPromise = this.readDefaultExtensions(); + } + } + + 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 => ({ + identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) }, + location: e.extensionLocation, type: ExtensionType.User, - packageJSON: data.packageJSON, - }); + packageJSON: e.packageJSON, + })); + } + + private async readDefaultUserWebExtensions(): Promise { + const result: IStaticExtension[] = []; + const defaultUserWebExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions') || []; + for (const webExtension of defaultUserWebExtensions) { + const extensionLocation = URI.parse(webExtension.location); + const manifestLocation = joinPath(extensionLocation, 'package.json'); + const context = await this.requestService.request({ type: 'GET', url: manifestLocation.toString(true) }, CancellationToken.None); + if (!isSuccess(context)) { + this.logService.warn('Skipped default user web extension as there is an error while fetching manifest', manifestLocation); + continue; + } + const content = await asText(context); + if (!content) { + this.logService.warn('Skipped default user web extension as there is manifest is not found', manifestLocation); + continue; + } + const packageJSON = JSON.parse(content); + result.push({ + packageJSON, + extensionLocation, + }); + } + return result; } async scanExtensions(type?: ExtensionType): Promise { @@ -76,7 +111,8 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService extensions.push(...systemExtensions); } if (type === undefined || type === ExtensionType.User) { - extensions.push(...this.staticExtensions); + 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 97e361d9a4..246163949a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -587,8 +587,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 4ab553b30c..db9ff0a3b3 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/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 475d1a1660..edf35e0e54 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -205,7 +205,7 @@ export class FileWalker { .map(arg => arg.match(/^-/) ? arg : `'${arg}'`) .join(' '); - let rgCmd = `rg ${escapedArgs}\n - cwd: ${ripgrep.cwd}`; + let rgCmd = `${ripgrep.rgDiskPath} ${escapedArgs}\n - cwd: ${ripgrep.cwd}`; if (ripgrep.rgArgs.siblingClauses) { rgCmd += `\n - Sibling clauses: ${JSON.stringify(ripgrep.rgArgs.siblingClauses)}`; } diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 224c42d5e6..28fe3a432a 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -23,6 +23,7 @@ export function spawnRipgrepCmd(config: IFileQuery, folderQuery: IFolderQuery, i const cwd = folderQuery.folder.fsPath; return { cmd: cp.spawn(rgDiskPath, rgArgs.args, { cwd }), + rgDiskPath, siblingClauses: rgArgs.siblingClauses, rgArgs, cwd diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index e60a038dba..dc1c6aed76 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -44,7 +44,7 @@ export class RipgrepTextSearchEngine { const escapedArgs = rgArgs .map(arg => arg.match(/^-/) ? arg : `'${arg}'`) .join(' '); - this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}`); + this.outputChannel.appendLine(`${rgDiskPath} ${escapedArgs}\n - cwd: ${cwd}`); let rgProc: Maybe = cp.spawn(rgDiskPath, rgArgs, { cwd }); rgProc.on('error', e => { @@ -57,6 +57,7 @@ export class RipgrepTextSearchEngine { const ripgrepParser = new RipgrepParser(options.maxResults, cwd, options.previewOptions); ripgrepParser.on('result', (match: TextSearchResult) => { gotResult = true; + dataWithoutResult = ''; progress.report(match); }); @@ -79,8 +80,12 @@ export class RipgrepTextSearchEngine { cancel(); }); + let dataWithoutResult = ''; rgProc.stdout!.on('data', data => { ripgrepParser.handleData(data); + if (!gotResult) { + dataWithoutResult += data; + } }); let gotData = false; @@ -96,7 +101,12 @@ export class RipgrepTextSearchEngine { rgProc.on('close', () => { this.outputChannel.appendLine(gotData ? 'Got data from stdout' : 'No data from stdout'); this.outputChannel.appendLine(gotResult ? 'Got result from parser' : 'No result from parser'); + if (dataWithoutResult) { + this.outputChannel.appendLine(`Got data without result: ${dataWithoutResult}`); + } + this.outputChannel.appendLine(''); + if (isDone) { resolve({ limitHit }); } else { diff --git a/src/vs/workbench/services/textfile/common/encoding.ts b/src/vs/workbench/services/textfile/common/encoding.ts index c88eb6a5db..70e5b3c04f 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/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 3da54495fc..6373e49041 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -75,6 +75,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private bufferSavedVersionId: number | undefined; private ignoreDirtyOnModelContentChange = false; + private static readonly UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD = 500; + private lastModelContentChangeFromUndoRedo: number | undefined = undefined; + private lastResolvedFileStat: IFileStatWithMetadata | undefined; private readonly saveSequentializer = new TaskSequentializer(); @@ -450,16 +453,23 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // where `value` was captured in the content change listener closure scope. // Content Change - this._register(model.onDidChangeContent(() => this.onModelContentChanged(model))); + this._register(model.onDidChangeContent(e => this.onModelContentChanged(model, e.isUndoing || e.isRedoing))); } - private onModelContentChanged(model: ITextModel): void { + private onModelContentChanged(model: ITextModel, isUndoingOrRedoing: boolean): void { this.logService.trace(`[text file model] onModelContentChanged() - enter`, this.resource.toString(true)); // In any case increment the version id because it tracks the textual content state of the model at all times this.versionId++; this.logService.trace(`[text file model] onModelContentChanged() - new versionId ${this.versionId}`, this.resource.toString(true)); + // Remember when the user changed the model through a undo/redo operation. + // We need this information to throttle save participants to fix + // https://github.com/microsoft/vscode/issues/102542 + if (isUndoingOrRedoing) { + this.lastModelContentChangeFromUndoRedo = Date.now(); + } + // We mark check for a dirty-state change upon model content change, unless: // - explicitly instructed to ignore it (e.g. from model.load()) // - the model is readonly (in that case we never assume the change was done by the user) @@ -639,7 +649,31 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save participants can also be skipped through API. if (this.isResolved() && !options.skipSaveParticipants) { try { - await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + + // Measure the time it took from the last undo/redo operation to this save. If this + // time is below `UNDO_REDO_SAVE_PARTICIPANTS_THROTTLE_THRESHOLD`, we make sure to + // delay the save participant for the remaining time if the reason is auto save. + // + // This fixes the following issue: + // - the user has configured auto save with delay of 100ms or shorter + // - the user has a save participant enabled that modifies the file on each save + // - the user types into the file and the file gets saved + // - the user triggers undo operation + // - this will undo the save participant change but trigger the save participant right after + // - the user has no chance to undo over the save participant + // + // Reported as: https://github.com/microsoft/vscode/issues/102542 + if (options.reason === SaveReason.AUTO && typeof this.lastModelContentChangeFromUndoRedo === 'number') { + const timeFromUndoRedoToSave = Date.now() - this.lastModelContentChangeFromUndoRedo; + if (timeFromUndoRedoToSave < TextFileEditorModel.UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD) { + await timeout(TextFileEditorModel.UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD - timeFromUndoRedoToSave); + } + } + + // Run save participants unless save was cancelled meanwhile + if (!saveCancellation.token.isCancellationRequested) { + await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + } } catch (error) { this.logService.error(`[text file model] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(true)); } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index d2e9332447..49f01b9a8a 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 098bf311a1..a59cd5898b 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 1630c966dd..ca8871e31d 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -6,7 +6,7 @@ import { IUserDataSyncService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResourceGroup, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -270,7 +270,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined); try { switch (action) { - case 'merge': return await manualSyncTask.merge(); + case 'merge': return await manualSyncTask.apply(); case 'pull': return await manualSyncTask.pull(); case 'push': return await manualSyncTask.push(); case 'manual': return; @@ -302,28 +302,28 @@ 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('sync manually', "Sync Manually"), localize('merge', "Merge"), localize('replace local', "Replace Local"), + 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) { case 0: - this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'manual' }); - return 'manual'; - case 1: this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' }); return 'merge'; - case 2: + case 1: this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'pull' }); return 'pull'; + case 2: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'manual' }); + return 'manual'; } this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' }); throw canceled(); @@ -337,14 +337,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat await this.waitForActiveSyncViews(); await this.viewsService.openView(MANUAL_SYNC_VIEW_ID); - await Event.toPromise(Event.filter(this.userDataSyncPreview.onDidChangeChanges, e => e.length === 0)); - if (this.userDataSyncPreview.conflicts.length) { - await Event.toPromise(Event.filter(this.userDataSyncPreview.onDidChangeConflicts, e => e.length === 0)); - } - - /* Merge to sync globalState changes */ - await task.merge(); - + const error = await Event.toPromise(this.userDataSyncPreview.onDidCompleteManualSync); this.userDataSyncPreview.unsetManualSyncPreview(); this.manualSyncViewEnablementContext.set(false); @@ -354,12 +347,16 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat const viewContainer = this.viewDescriptorService.getViewContainerByViewId(MANUAL_SYNC_VIEW_ID); this.viewsService.closeViewContainer(viewContainer!.id); } + + 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"), }); @@ -560,16 +557,18 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { - private _changes: ReadonlyArray = []; - get changes() { return Object.freeze(this._changes); } - private _onDidChangeChanges = this._register(new Emitter>()); - readonly onDidChangeChanges = this._onDidChangeChanges.event; + private _resources: ReadonlyArray = []; + get resources() { return Object.freeze(this._resources); } + private _onDidChangeResources = this._register(new Emitter>()); + readonly onDidChangeResources = this._onDidChangeResources.event; - private _conflicts: ReadonlyArray = []; + private _conflicts: ReadonlyArray = []; get conflicts() { return Object.freeze(this._conflicts); } - private _onDidChangeConflicts = this._register(new Emitter>()); + private _onDidChangeConflicts = this._register(new Emitter>()); readonly onDidChangeConflicts = this._onDidChangeConflicts.event; + private _onDidCompleteManualSync = this._register(new Emitter()); + readonly onDidCompleteManualSync = this._onDidCompleteManualSync.event; private manualSync: { preview: [SyncResource, ISyncResourcePreview][], task: IManualSyncTask, disposables: DisposableStore } | undefined; constructor( @@ -583,7 +582,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { setManualSyncPreview(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): void { const disposables = new DisposableStore(); this.manualSync = { task, preview, disposables }; - this.updateChanges(); + this.updateResources(); } unsetManualSyncPreview(): void { @@ -591,19 +590,19 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { this.manualSync.disposables.dispose(); this.manualSync = undefined; } - this.updateChanges(); + 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); } else { - await this.userDataSyncService.acceptPreviewContent(syncResource, resource, content); + await this.userDataSyncService.accept(syncResource, resource, content, false); } } - async merge(resource?: URI): Promise { + async merge(resource: URI): Promise { if (!this.manualSync) { throw new Error('Can merge only while syncing manually'); } @@ -611,6 +610,41 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { this.updatePreview(syncPreview); } + async discard(resource: URI): Promise { + if (!this.manualSync) { + throw new Error('Can discard only while syncing manually'); + } + const syncPreview = await this.manualSync.task.discard(resource); + this.updatePreview(syncPreview); + } + + async apply(): Promise { + if (!this.manualSync) { + throw new Error('Can apply only while syncing manually'); + } + + 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); + } + } + + async cancel(): Promise { + if (!this.manualSync) { + throw new Error('Can cancel only while syncing manually'); + } + await this.manualSync.task.stop(); + this.updatePreview([]); + this._onDidCompleteManualSync.fire(canceled()); + } + async pull(): Promise { if (!this.manualSync) { throw new Error('Can pull only while syncing manually'); @@ -630,7 +664,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { private updatePreview(preview: [SyncResource, ISyncResourcePreview][]) { if (this.manualSync) { this.manualSync.preview = preview; - this.updateChanges(); + this.updateResources(); } } @@ -640,34 +674,28 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { this._conflicts = newConflicts; this._onDidChangeConflicts.fire(this.conflicts); } - this.updateChanges(); } - private updateChanges(): void { - const newChanges = this.toUserDataSyncResourceGroups( + private updateResources(): void { + const newResources = this.toUserDataSyncResourceGroups( (this.manualSync?.preview || []) - .filter(([syncResource]) => syncResource !== SyncResource.GlobalState) /* Filter Global State Changes */ .map(([syncResource, syncResourcePreview]) => ([ syncResource, - /* remove merged previews and conflicts and with no changes and conflicts */ - syncResourcePreview.resourcePreviews.filter(r => - !r.merged - && (r.localChange !== Change.None || r.remoteChange !== Change.None) - && !this._conflicts.some(c => c.syncResource === syncResource && isEqual(c.local, r.localResource))) + syncResourcePreview.resourcePreviews ])) ); - if (!equals(newChanges, this._changes, (a, b) => isEqual(a.local, b.local))) { - this._changes = newChanges; - this._onDidChangeChanges.fire(this.changes); + if (!equals(newResources, this._resources, (a, b) => isEqual(a.local, b.local) && a.mergeState === b.mergeState)) { + this._resources = newResources; + this._onDidChangeResources.fire(this.resources); } } - private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResourceGroup[] { + private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResource[] { return flatten( syncResourcePreviews.map(([syncResource, resourcePreviews]) => - resourcePreviews.map(({ localResource, remoteResource, previewResource, localChange, remoteChange }) => - ({ syncResource, local: localResource, remote: remoteResource, preview: previewResource, localChange, remoteChange }))) + resourcePreviews.map(({ localResource, remoteResource, previewResource, acceptedResource, localChange, remoteChange, mergeState }) => + ({ syncResource, local: localResource, remote: remoteResource, merged: previewResource, accepted: acceptedResource, localChange, remoteChange, mergeState }))) ); } diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index fa5e47705e..811314b4c8 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IAuthenticationProvider, SyncStatus, SyncResource, Change } from 'vs/platform/userDataSync/common/userDataSync'; +import { IAuthenticationProvider, SyncStatus, SyncResource, Change, MergeState } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; @@ -17,25 +17,27 @@ export interface IUserDataSyncAccount { } export interface IUserDataSyncPreview { - readonly onDidChangeChanges: Event>; - readonly changes: ReadonlyArray; + readonly onDidChangeResources: Event>; + readonly resources: ReadonlyArray; - onDidChangeConflicts: Event>; - readonly conflicts: 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; push(): Promise; + apply(): Promise; + cancel(): Promise; } -export interface IUserDataSyncResourceGroup { +export interface IUserDataSyncResource { readonly syncResource: SyncResource; readonly local: URI; readonly remote: URI; - readonly preview: URI; + readonly merged: URI; + readonly accepted: URI; readonly localChange: Change; readonly remoteChange: Change; + readonly mergeState: MergeState; } export const IUserDataSyncWorkbenchService = createDecorator('IUserDataSyncWorkbenchService'); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index f1da89330b..0d91edf265 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -102,8 +102,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('hasLocalData'); } - acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string): Promise { - return this.channel.call('acceptPreviewContent', [syncResource, resource, content]); + accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise { + return this.channel.call('accept', [syncResource, resource, content, apply]); } resolveContent(resource: URI): Promise { @@ -186,16 +186,26 @@ 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); } - async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> { + async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> { const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('merge', [resource]); return this.deserializePreviews(previews); } + async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> { + const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('discard', [resource]); + return this.deserializePreviews(previews); + } + + async apply(): Promise<[SyncResource, ISyncResourcePreview][]> { + const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('apply'); + return this.deserializePreviews(previews); + } + pull(): Promise { return this.channel.call('pull'); } @@ -223,6 +233,7 @@ class ManualSyncTask implements IManualSyncTask { localResource: URI.revive(r.localResource), remoteResource: URI.revive(r.remoteResource), previewResource: URI.revive(r.previewResource), + acceptedResource: URI.revive(r.acceptedResource), })) } ])); diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 11b75daf16..0dc925be42 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -47,6 +47,7 @@ import 'vs/editor/contrib/links/getLinks'; import 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/suggest/suggest'; +import 'vs/editor/contrib/rename/rename'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = createTextModel( @@ -232,6 +233,27 @@ suite('ExtHostLanguageFeatureCommands', function () { }); + // --- rename + test('vscode.executeDocumentRenameProvider', async function () { + disposables.push(extHost.registerRenameProvider(nullExtensionDescription, defaultSelector, new class implements vscode.RenameProvider { + provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string) { + const edit = new types.WorkspaceEdit(); + edit.insert(document.uri, position, newName); + return edit; + } + })); + + await rpcProtocol.sync(); + + const edit = await commands.executeCommand('vscode.executeDocumentRenameProvider', model.uri, new types.Position(0, 12), 'newNameOfThis'); + + assert.ok(edit); + assert.equal(edit.has(model.uri), true); + const textEdits = edit.get(model.uri); + assert.equal(textEdits.length, 1); + assert.equal(textEdits[0].newText, 'newNameOfThis'); + }); + // --- definition test('Definition, invalid arguments', function () { diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts new file mode 100644 index 0000000000..8d3717fc8c --- /dev/null +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { mock } from 'vs/base/test/common/mock'; +import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookDocument, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { URI } from 'vs/base/common/uri'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; + +suite('NotebookCell', function () { + + let rpcProtocol: TestRPCProtocol; + let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors; + + const disposables = new DisposableStore(); + const fakeNotebookProxy = new class extends mock() { }; + const fakeNotebook = new class extends mock() { }; + + setup(async function () { + disposables.clear(); + rpcProtocol = new TestRPCProtocol(); + extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); + }); + + test('Document is real', function () { + + const dto = { + cellKind: CellKind.Code, + eol: '\n', + source: ['aaaa', 'bbbb', 'cccc'], + handle: 0, + language: 'fooLang', + outputs: [], + uri: URI.parse('test:/path') + }; + const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); + + assert.ok(cell.document); + assert.strictEqual(cell.document.version, 0); + assert.strictEqual(cell.document.languageId, dto.language); + assert.strictEqual(cell.document.uri.toString(), dto.uri.toString()); + assert.strictEqual(cell.uri.toString(), dto.uri.toString()); + }); + + + test('Document is uses actual document when possible', function () { + + const dto = { + cellKind: CellKind.Code, + eol: '\n', + source: ['aaaa', 'bbbb', 'cccc'], + handle: 0, + language: 'fooLang', + outputs: [], + uri: URI.parse('test:/path') + }; + const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); + + // this is the "default document" which is used when the real + // document isn't open + const documentNow = cell.document; + + extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ + addedDocuments: [{ + isDirty: false, + versionId: 12, + modeId: dto.language, + uri: dto.uri, + lines: dto.source, + EOL: dto.eol + }] + }); + + // the real document + assert.ok(documentNow !== cell.document); + assert.strictEqual(cell.document.languageId, dto.language); + assert.strictEqual(cell.document.uri.toString(), dto.uri.toString()); + assert.strictEqual(cell.uri.toString(), dto.uri.toString()); + + // back to "default document" + extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: [dto.uri] }); + assert.ok(documentNow === cell.document); + }); + + test('Document can change language (1/2)', function () { + + const dto = { + cellKind: CellKind.Code, + eol: '\n', + source: ['aaaa', 'bbbb', 'cccc'], + handle: 0, + language: 'fooLang', + outputs: [], + uri: URI.parse('test:/path') + }; + const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); + + assert.strictEqual(cell.document.languageId, dto.language); + cell.defaultDocument._acceptLanguageId('barLang'); + assert.strictEqual(cell.document.languageId, 'barLang'); + }); + + + test('Document can change language (1/2)', function () { + + + const dto = { + cellKind: CellKind.Code, + eol: '\n', + source: ['aaaa', 'bbbb', 'cccc'], + handle: 0, + language: 'fooLang', + outputs: [], + uri: URI.parse('test:/path') + }; + + extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ + addedDocuments: [{ + isDirty: false, + versionId: 12, + modeId: dto.language, + uri: dto.uri, + lines: dto.source, + EOL: dto.eol + }] + }); + + const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + + const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); + + // a real document already exists and therefore + // the "default document" doesn't count + + assert.strictEqual(cell.document.languageId, dto.language); + cell.defaultDocument._acceptLanguageId('barLang'); + assert.strictEqual(cell.document.languageId, dto.language); + + extHostDocuments.$acceptModelModeChanged(dto.uri, dto.language, 'barLang'); + assert.strictEqual(cell.document.languageId, 'barLang'); + }); + +}); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index d23d5b6f6f..9c3ae612ba 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -57,6 +57,7 @@ suite('NotebookConcatDocument', function () { handle: 0, uri: CellUri.generate(notebookUri, 0), source: ['### Heading'], + eol: '\n', language: 'markdown', cellKind: CellKind.Markdown, outputs: [], @@ -123,6 +124,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: cellUri1, source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -130,6 +132,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: cellUri2, source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -155,6 +158,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -162,6 +166,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -194,6 +199,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -216,6 +222,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -257,6 +264,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -264,6 +272,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -320,6 +329,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['fooLang-document'], + eol: '\n', language: 'fooLang', cellKind: CellKind.Code, outputs: [], @@ -327,6 +337,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['barLang-document'], + eol: '\n', language: 'barLang', cellKind: CellKind.Code, outputs: [], @@ -348,6 +359,7 @@ suite('NotebookConcatDocument', function () { handle: 3, uri: CellUri.generate(notebook.uri, 3), source: ['barLang-document2'], + eol: '\n', language: 'barLang', cellKind: CellKind.Code, outputs: [], @@ -381,6 +393,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -388,6 +401,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -432,6 +446,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -439,6 +454,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -467,6 +483,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -474,6 +491,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -499,6 +517,7 @@ suite('NotebookConcatDocument', function () { handle: 1, uri: CellUri.generate(notebook.uri, 1), source: ['Hello', 'World', 'Hello World!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], @@ -506,6 +525,7 @@ suite('NotebookConcatDocument', function () { handle: 2, uri: CellUri.generate(notebook.uri, 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', language: 'test', cellKind: CellKind.Code, outputs: [], diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts index 511518e4fe..8e474a08b8 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts @@ -150,14 +150,14 @@ class NonSerializableTestEditorInput extends EditorInput { class TestFileEditorInput extends EditorInput implements IFileEditorInput { - readonly label = this.resource; + readonly preferredResource = this.resource; constructor(public id: string, public resource: URI) { super(); } getTypeId() { return 'testFileEditorInputForGroups'; } resolve(): Promise { return Promise.resolve(null!); } - setLabel(label: URI): void { } + setPreferredResource(resource: URI): void { } setEncoding(encoding: string) { } getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 0a187d167c..6d230b9437 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1123,7 +1123,7 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: EditorInput): boolean { return !!(other?.resource && this.resource.toString() === other.resource.toString() && other instanceof TestFileEditorInput && other.getTypeId() === this.typeId); } - setLabel(label: URI): void { } + setPreferredResource(resource: URI): void { } setEncoding(encoding: string) { } getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index a0675f8758..e6bf9eefc4 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/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 59bd9814dc..13f2faa1ff 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -18,6 +18,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IProductConfiguration } from 'vs/platform/product/common/productService'; +import { mark } from 'vs/base/common/performance'; interface IResourceUriProvider { (uri: URI): URI; @@ -294,6 +295,11 @@ interface IWorkbenchConstructionOptions { */ readonly defaultLayout?: IDefaultLayout; + /** + * Optional configuration default overrides contributed to the workbench. + */ + readonly configurationDefaults?: Record; + //#endregion @@ -359,6 +365,10 @@ let workbenchPromiseResolve: Function; const workbenchPromise = new Promise(resolve => workbenchPromiseResolve = resolve); async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { + // Mark start of workbench + mark('didLoadWorkbenchMain'); + performance.mark('workbench-start'); + // Assert that the workbench is not created more than once. We currently // do not support this and require a full context switch to clean-up. if (created) { diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 0418b0029f..2855358b7e 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') @@ -121,14 +125,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 a5923e1dea..efe2d63722 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -34,7 +34,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`, 'sql': `../${outdir}/sql`, // {{SQL CARBON EDIT}} diff --git a/yarn.lock b/yarn.lock index bab739df16..3993675ed7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2851,10 +2851,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"