diff --git a/.github/actions/build-chat/.gitignore b/.github/actions/build-chat/.gitignore deleted file mode 100644 index db4c6d9b679..00000000000 --- a/.github/actions/build-chat/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -node_modules \ No newline at end of file diff --git a/.github/actions/build-chat/action.yml b/.github/actions/build-chat/action.yml deleted file mode 100644 index 278475c9c01..00000000000 --- a/.github/actions/build-chat/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: 'Build Chat' -description: 'Notify in chat about build results.' -author: 'Christof Marti' -inputs: - workflow_run_url: - description: 'Workflow run URL of the completed build.' - required: true -runs: - using: 'node12' - main: 'dist/main.js' diff --git a/.github/actions/build-chat/package.json b/.github/actions/build-chat/package.json deleted file mode 100644 index 156717a823e..00000000000 --- a/.github/actions/build-chat/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "build-chat", - "version": "0.0.0", - "author": "Microsoft Corporation", - "license": "MIT", - "description": "A GitHub action to create a Windows Package Manager manifest file.", - "main": "dist/main.js", - "scripts": { - "build": "tsc" - }, - "dependencies": { - "@actions/core": "^1.2.6", - "@octokit/rest": "^18.0.12", - "@slack/web-api": "^6.0.0", - "azure-storage": "^2.10.3", - "stream-buffers": "^3.0.2" - }, - "devDependencies": { - "@types/node": "^14.14.22", - "@types/stream-buffers": "^3.0.3", - "typescript": "^4.1.3" - } -} diff --git a/.github/actions/build-chat/src/main.ts b/.github/actions/build-chat/src/main.ts deleted file mode 100644 index 71e035850f7..00000000000 --- a/.github/actions/build-chat/src/main.ts +++ /dev/null @@ -1,217 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as core from '@actions/core'; -import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; -import { WebClient } from '@slack/web-api'; -import * as storage from 'azure-storage'; -import { WritableStreamBuffer } from 'stream-buffers'; - -(async () => { - const actionUrl = core.getInput('workflow_run_url'); - const url = actionUrl || 'https://api.github.com/repos/microsoft/vscode/actions/runs/503514090'; - console.log(url); - const parts = url.split('/'); - const owner = parts[parts.length - 5]; - const repo = parts[parts.length - 4]; - const runId = parseInt(parts[parts.length - 1], 10); - if (actionUrl) { - await handleNotification(owner, repo, runId); - } else { - const results = await buildComplete(owner, repo, runId); - for (const message of [...results.logMessages, ...results.messages]) { - console.log(message); - } - } -})() - .then(null, console.error); - -const testChannels = ['bot-log', 'bot-test-log']; - -async function handleNotification(owner: string, repo: string, runId: number) { - - const results = await buildComplete(owner, repo, runId); - if (results.logMessages.length || results.messages.length) { - - const web = new WebClient(process.env.SLACK_TOKEN); - const memberships = await listAllMemberships(web); - const memberTestChannels = memberships.filter(m => testChannels.indexOf(m.name) !== -1); - - for (const message of results.logMessages) { - for (const testChannel of memberTestChannels) { - await web.chat.postMessage({ - text: message, - link_names: true, - channel: testChannel.id, - }); - } - } - for (const message of results.messages) { - for (const channel of memberships) { - await web.chat.postMessage({ - text: message, - link_names: true, - channel: channel.id, - }); - } - } - } -} - -async function buildComplete(owner: string, repo: string, runId: number) { - console.log(`buildComplete: https://github.com/${owner}/${repo}/actions/runs/${runId}`); - const auth = `token ${process.env.GITHUB_TOKEN}`; - const octokit = new Octokit({ auth }); - const buildResult = (await octokit.actions.getWorkflowRun({ - owner, - repo, - run_id: runId, - })).data; - if (buildResult.head_branch !== 'master' && !buildResult.head_branch?.startsWith('release/')) { - console.error('Private branch. Terminating.') - return { logMessages: [], messages: [] }; - } - - // const buildQuery = `${buildsApiUrl}?$top=10&maxTime=${buildResult.finishTime}&definitions=${buildResult.definition.id}&branchName=${buildResult.sourceBranch}&resultFilter=${results.join(',')}&api-version=5.0-preview.4`; - - const buildResults = (await octokit.actions.listWorkflowRuns({ - owner, - repo, - workflow_id: buildResult.workflow_id, - branch: buildResult.head_branch || undefined, - per_page: 5, // More returns 502s. - })).data.workflow_runs - .filter(run => run.status === 'completed'); - - const currentBuildIndex = buildResults.findIndex(build => build.id === buildResult.id); - if (currentBuildIndex === -1) { - console.error('Build not on first page. Terminating.') - console.error(buildResults.map(({ id, status, conclusion }) => ({ id, status, conclusion }))); - return { logMessages: [], messages: [] }; - } - const slicedResults = buildResults.slice(currentBuildIndex, currentBuildIndex + 2); - const builds = slicedResults - .map((build, i, array) => ({ - data: build, - previousSourceVersion: i < array.length - 1 ? array[i + 1].head_sha : undefined, - authors: [], - buildHtmlUrl: build.html_url, - changesHtmlUrl: '', - })); - const logMessages = builds.slice(0, 1) - .map(build => `Id: ${build.data.id} | Branch: ${build.data.head_branch} | Conclusion: ${build.data.conclusion} | Created: ${build.data.created_at} | Updated: ${build.data.updated_at}`); - const transitionedBuilds = builds.filter((build, i, array) => i < array.length - 1 && transitioned(build, array[i + 1])); - await Promise.all(transitionedBuilds - .map(async build => { - if (build.previousSourceVersion) { - const cmp = await compareCommits(octokit, owner, repo, build.previousSourceVersion, build.data.head_sha); - const commits = cmp.data.commits; - const authors = new Set([ - ...commits.map((c: any) => c.author.login), - ...commits.map((c: any) => c.committer.login), - ]); - authors.delete('web-flow'); // GitHub Web UI committer - build.authors = [...authors]; - build.changesHtmlUrl = `https://github.com/${owner}/${repo}/compare/${build.previousSourceVersion.substr(0, 7)}...${build.data.head_sha.substr(0, 7)}`; // Shorter than: cmp.data.html_url - } - })); - const vscode = repo === 'vscode'; - const name = vscode ? `VS Code ${buildResult.name} Build` : buildResult.name; - // TBD: `Requester: ${vstsToSlackUser(build.requester, build.degraded)}${pingBenForSmokeTests && releaseBuild && build.result === 'partiallySucceeded' ? ' | Ping: @bpasero' : ''}` - const accounts = await readAccounts(); - const githubAccountMap = githubToAccounts(accounts); - const messages = transitionedBuilds.map(build => `${name} -Result: ${build.data.conclusion} | Branch: ${build.data.head_branch} | Authors: ${githubToSlackUsers(githubAccountMap, build.authors, build.degraded).sort().join(', ') || `None (rebuild)`} -Build: ${build.buildHtmlUrl} -Changes: ${build.changesHtmlUrl}`); - return { logMessages, messages }; -} - -const conclusions = ['success', 'failure'] - -function transitioned(newer: Build, older: Build) { - const newerResult = newer.data.conclusion || 'success'; - const olderResult = older.data.conclusion || 'success'; - if (newerResult === olderResult) { - return false; - } - if (conclusions.indexOf(newerResult) > conclusions.indexOf(olderResult)) { - newer.degraded = true; - } - return true; -} - -async function compareCommits(octokit: Octokit, owner: string, repo: string, base: string, head: string) { - return octokit.repos.compareCommits({ owner, repo, base, head }); -} - -function githubToSlackUsers(githubToAccounts: Record, githubUsers: string[], at?: boolean) { - return githubUsers.map(g => githubToAccounts[g] ? `${at ? '@' : ''}${githubToAccounts[g].slack}` : g); -} - -interface Accounts { - github: string; - slack: string; - vsts: string; -} - -function githubToAccounts(accounts: Accounts[]) { - return accounts.reduce((m, e) => { - m[e.github] = e; - return m; - }, >{}); -} - -async function readAccounts() { - const connectionString = process.env.BUILD_CHAT_STORAGE_CONNECTION_STRING; - if (!connectionString) { - console.error('Connection string missing.'); - return []; - } - const buf = await readFile(connectionString, 'config', '/', 'accounts.json'); - return JSON.parse(buf.toString()) as Accounts[]; -} - -async function readFile(connectionString: string, share: string, directory: string, filename: string) { - return new Promise((resolve, reject) => { - const stream = new WritableStreamBuffer() - const fileService = storage.createFileService(connectionString); - fileService.getFileToStream(share, directory, filename, stream, err => { - if (err) { - reject(err); - } else { - const contents = stream.getContents(); - if (contents) { - resolve(contents); - } else { - reject(new Error('No content')); - } - } - }); - }); -} - -interface AllChannels { - channels: { - id: string; - name: string; - is_member: boolean; - }[]; -} - -async function listAllMemberships(web: WebClient) { - const groups = await web.conversations.list({ types: 'public_channel,private_channel' }) as unknown as AllChannels; - return groups.channels - .filter(c => c.is_member); -} - -interface Build { - data: RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']; - previousSourceVersion: string | undefined; - authors: string[]; - buildHtmlUrl: string; - changesHtmlUrl: string; - degraded?: boolean; -} diff --git a/.github/actions/build-chat/tsconfig.json b/.github/actions/build-chat/tsconfig.json deleted file mode 100644 index f4889aab7bd..00000000000 --- a/.github/actions/build-chat/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "strict": true, - "noUnusedLocals": true, - "resolveJsonModule": true, - "lib": [ - "es2017" - ], - "sourceMap": true, - "outDir": "./dist", - "rootDir": "./src", - }, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/.github/actions/build-chat/yarn.lock b/.github/actions/build-chat/yarn.lock deleted file mode 100644 index bc2185232d2..00000000000 --- a/.github/actions/build-chat/yarn.lock +++ /dev/null @@ -1,728 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@actions/core@^1.2.6": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.6.tgz#a78d49f41a4def18e88ce47c2cac615d5694bf09" - integrity sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA== - -"@octokit/auth-token@^2.4.4": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.4.tgz#ee31c69b01d0378c12fd3ffe406030f3d94d3b56" - integrity sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q== - dependencies: - "@octokit/types" "^6.0.0" - -"@octokit/core@^3.2.3": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.2.4.tgz#5791256057a962eca972e31818f02454897fd106" - integrity sha512-d9dTsqdePBqOn7aGkyRFe7pQpCXdibSJ5SFnrTr0axevObZrpz3qkWm7t/NjYv5a66z6vhfteriaq4FRz3e0Qg== - dependencies: - "@octokit/auth-token" "^2.4.4" - "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.4.12" - "@octokit/types" "^6.0.3" - before-after-hook "^2.1.0" - universal-user-agent "^6.0.0" - -"@octokit/endpoint@^6.0.1": - version "6.0.10" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.10.tgz#741ce1fa2f4fb77ce8ebe0c6eaf5ce63f565f8e8" - integrity sha512-9+Xef8nT7OKZglfkOMm7IL6VwxXUQyR7DUSU0LH/F7VNqs8vyd7es5pTfz9E7DwUIx7R3pGscxu1EBhYljyu7Q== - dependencies: - "@octokit/types" "^6.0.0" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/graphql@^4.5.8": - version "4.5.8" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.8.tgz#d42373633c3015d0eafce64a8ce196be167fdd9b" - integrity sha512-WnCtNXWOrupfPJgXe+vSmprZJUr0VIu14G58PMlkWGj3cH+KLZEfKMmbUQ6C3Wwx6fdhzVW1CD5RTnBdUHxhhA== - dependencies: - "@octokit/request" "^5.3.0" - "@octokit/types" "^6.0.0" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-3.0.0.tgz#f73d48af2d21bf4f97fbf38fae43b54699e0dbba" - integrity sha512-jOp1CVRw+OBJaZtG9QzZggvJXvyzgDXuW948SWsDiwmyDuCjeYCiF3TDD/qvhpF580RfP7iBIos4AVU6yhgMlA== - -"@octokit/plugin-paginate-rest@^2.6.2": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.8.0.tgz#2b41e12b494e895bf5fb5b12565d2c80a0ecc6ae" - integrity sha512-HtuEQ2AYE4YFEBQN0iHmMsIvVucd5RsnwJmRKIsfAg1/ZeoMaU+jXMnTAZqIUEmcVJA27LjHUm3f1hxf8Fpdxw== - dependencies: - "@octokit/types" "^6.4.0" - -"@octokit/plugin-request-log@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44" - integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg== - -"@octokit/plugin-rest-endpoint-methods@4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.4.1.tgz#105cf93255432155de078c9efc33bd4e14d1cd63" - integrity sha512-+v5PcvrUcDeFXf8hv1gnNvNLdm4C0+2EiuWt9EatjjUmfriM1pTMM+r4j1lLHxeBQ9bVDmbywb11e3KjuavieA== - dependencies: - "@octokit/types" "^6.1.0" - deprecation "^2.3.1" - -"@octokit/request-error@^2.0.0": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.4.tgz#07dd5c0521d2ee975201274c472a127917741262" - integrity sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA== - dependencies: - "@octokit/types" "^6.0.0" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^5.3.0", "@octokit/request@^5.4.12": - version "5.4.12" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.12.tgz#b04826fa934670c56b135a81447be2c1723a2ffc" - integrity sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - once "^1.4.0" - universal-user-agent "^6.0.0" - -"@octokit/rest@^18.0.12": - version "18.0.12" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.12.tgz#278bd41358c56d87c201e787e8adc0cac132503a" - integrity sha512-hNRCZfKPpeaIjOVuNJzkEL6zacfZlBPV8vw8ReNeyUkVvbuCvvrrx8K8Gw2eyHHsmd4dPlAxIXIZ9oHhJfkJpw== - dependencies: - "@octokit/core" "^3.2.3" - "@octokit/plugin-paginate-rest" "^2.6.2" - "@octokit/plugin-request-log" "^1.0.2" - "@octokit/plugin-rest-endpoint-methods" "4.4.1" - -"@octokit/types@^6.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.1.0", "@octokit/types@^6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.4.0.tgz#f3f47be70bcdb3c26f2c2619f3dd0ced466a265c" - integrity sha512-1FEmuVppZE2zG0rBdQlviRz5cp0udyI63zyhBVPrm0FRNAsQkAXU7IYWQg1XvhChFut8YbFYN1usQpk54D6/4w== - dependencies: - "@octokit/openapi-types" "^3.0.0" - "@types/node" ">= 8" - -"@slack/logger@>=1.0.0 <3.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-2.0.0.tgz#6a4e1c755849bc0f66dac08a8be54ce790ec0e6b" - integrity sha512-OkIJpiU2fz6HOJujhlhfIGrc8hB4ibqtf7nnbJQDerG0BqwZCfmgtK5sWzZ0TkXVRBKD5MpLrTmCYyMxoMCgPw== - dependencies: - "@types/node" ">=8.9.0" - -"@slack/types@^1.7.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a" - integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg== - -"@slack/web-api@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.0.0.tgz#14c65ed73c66a187e5f20e12c3898dfd8d5cbf7c" - integrity sha512-YD1wqWuzrYPf4RQyD7OnYS5lImUmNWn+G5V6Qt0N97fPYxqhT72YJtRdSnsTc3VkH5R5imKOhYxb+wqI9hiHnA== - dependencies: - "@slack/logger" ">=1.0.0 <3.0.0" - "@slack/types" "^1.7.0" - "@types/is-stream" "^1.1.0" - "@types/node" ">=12.0.0" - axios "^0.21.1" - eventemitter3 "^3.1.0" - form-data "^2.5.0" - is-stream "^1.1.0" - p-queue "^6.6.1" - p-retry "^4.0.0" - -"@types/is-stream@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" - integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== - dependencies: - "@types/node" "*" - -"@types/node@*", "@types/node@>= 8", "@types/node@>=12.0.0", "@types/node@>=8.9.0", "@types/node@^14.14.22": - version "14.14.22" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18" - integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== - -"@types/retry@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/stream-buffers@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/stream-buffers/-/stream-buffers-3.0.3.tgz#34e565bf64e3e4bdeee23fd4aa58d4636014a02b" - integrity sha512-NeFeX7YfFZDYsCfbuaOmFQ0OjSmHreKBpp7MQ4alWQBHeh2USLsj7qyMyn9t82kjqIX516CR/5SRHnARduRtbQ== - dependencies: - "@types/node" "*" - -ajv@^6.12.3: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== - dependencies: - follow-redirects "^1.10.0" - -azure-storage@^2.10.3: - version "2.10.3" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.3.tgz#c5966bf929d87587d78f6847040ea9a4b1d4a50a" - integrity sha512-IGLs5Xj6kO8Ii90KerQrrwuJKexLgSwYC4oLWmc11mzKe7Jt2E5IVg+ZQ8K53YWZACtVTMBNO3iGuA+4ipjJxQ== - dependencies: - browserify-mime "~1.2.9" - extend "^3.0.2" - json-edm-parser "0.1.2" - md5.js "1.3.4" - readable-stream "~2.0.0" - request "^2.86.0" - underscore "~1.8.3" - uuid "^3.0.0" - validator "~9.4.1" - xml2js "0.2.8" - xmlbuilder "^9.0.7" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -before-after-hook@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" - integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== - -browserify-mime@~1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" - integrity sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8= - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -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= - -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== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -eventemitter3@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -extend@^3.0.2, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -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= - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-edm-parser@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" - integrity sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ= - dependencies: - jsonparse "~1.2.0" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jsonparse@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" - integrity sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -md5.js@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0= - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -mime-db@1.45.0: - version "1.45.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" - integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.28" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" - integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== - dependencies: - mime-db "1.45.0" - -node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -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" - -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= - -p-queue@^6.6.1: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-retry@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.2.0.tgz#ea9066c6b44f23cab4cd42f6147cdbbc6604da5d" - integrity sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA== - dependencies: - "@types/retry" "^0.12.0" - retry "^0.12.0" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -request@^2.86.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.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== - -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -stream-buffers@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" - integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -typescript@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== - -underscore@~1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= - -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== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@^3.0.0, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -validator@~9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" - integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xml2js@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" - integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I= - dependencies: - sax "0.5.x" - -xmlbuilder@^9.0.7: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= diff --git a/.github/workflows/build-chat.yml b/.github/workflows/build-chat.yml index e8a6797b810..f9f146164ba 100644 --- a/.github/workflows/build-chat.yml +++ b/.github/workflows/build-chat.yml @@ -14,23 +14,22 @@ jobs: main: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Node.js 12.x - uses: actions/setup-node@v1.4.4 - with: - node-version: "12.x" - - - name: Build - run: yarn install && yarn run build - working-directory: .github/actions/build-chat - - - name: Build Chat - uses: ./.github/actions/build-chat - with: - workflow_run_url: ${{ github.event.workflow_run.url }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - BUILD_CHAT_STORAGE_CONNECTION_STRING: ${{ secrets.BUILD_CHAT_STORAGE_CONNECTION_STRING }} + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: "microsoft/vscode-github-triage-actions" + path: ./actions + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Install Additional Dependencies + # Pulls in a bunch of other packages that arent needed for the rest of the actions + run: npm install @azure/storage-blob@12.1.1 + - name: Build Chat + uses: ./actions/build-chat + with: + token: ${{ secrets.GITHUB_TOKEN }} + slack_token: ${{ secrets.SLACK_TOKEN }} + storage_connection_string: ${{ secrets.BUILD_CHAT_STORAGE_CONNECTION_STRING }} + workflow_run_url: ${{ github.event.workflow_run.url }} + notification_channel: build + log_channel: bot-log diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 4286f2e8819..4d376246944 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -84,6 +84,8 @@ parameters: variables: - name: ENABLE_TERRAPIN value: ${{ eq(parameters.ENABLE_TERRAPIN, true) }} + - name: VSCODE_QUALITY + value: ${{ parameters.VSCODE_QUALITY }} - name: VSCODE_BUILD_STAGE_WINDOWS value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_LINUX diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index a3f54427a77..29afd985777 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -823,8 +823,7 @@ export class Repository { return this.repositoryRoot; } - // TODO@Joao: rename to exec - async run(args: string[], options: SpawnOptions = {}): Promise> { + async exec(args: string[], options: SpawnOptions = {}): Promise> { return await this.git.exec(this.repositoryRoot, args, options); } @@ -849,7 +848,7 @@ export class Repository { args.push(value); } - const result = await this.run(args, options); + const result = await this.exec(args, options); return result.stdout.trim(); } @@ -862,7 +861,7 @@ export class Repository { args.push('-l'); - const result = await this.run(args); + const result = await this.exec(args); const lines = result.stdout.trim().split(/\r|\r\n|\n/); return lines.map(entry => { @@ -878,7 +877,7 @@ export class Repository { args.push(options.path); } - const result = await this.run(args); + const result = await this.exec(args); if (result.exitCode) { // An empty repo return []; @@ -909,7 +908,7 @@ export class Repository { args.push('--', uri.fsPath); - const result = await this.run(args); + const result = await this.exec(args); if (result.exitCode) { // No file history, e.g. a new file or untracked return []; @@ -964,7 +963,7 @@ export class Repository { } const { mode, object } = elements[0]; - const catFile = await this.run(['cat-file', '-s', object]); + const catFile = await this.exec(['cat-file', '-s', object]); const size = parseInt(catFile.stdout); return { mode, object, size }; @@ -981,12 +980,12 @@ export class Repository { } async lstree(treeish: string, path: string): Promise { - const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', sanitizePath(path)]); + const { stdout } = await this.exec(['ls-tree', '-l', treeish, '--', sanitizePath(path)]); return parseLsTree(stdout); } async lsfiles(path: string): Promise { - const { stdout } = await this.run(['ls-files', '--stage', '--', sanitizePath(path)]); + const { stdout } = await this.exec(['ls-files', '--stage', '--', sanitizePath(path)]); return parseLsFiles(stdout); } @@ -1051,7 +1050,7 @@ export class Repository { } try { - await this.run(args); + await this.exec(args); } catch (err) { if (/patch does not apply/.test(err.stderr)) { err.gitErrorCode = GitErrorCodes.PatchDoesNotApply; @@ -1068,7 +1067,7 @@ export class Repository { args.push('--cached'); } - const result = await this.run(args); + const result = await this.exec(args); return result.stdout; } @@ -1081,7 +1080,7 @@ export class Repository { } const args = ['diff', '--', sanitizePath(path)]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout; } @@ -1094,7 +1093,7 @@ export class Repository { } const args = ['diff', ref, '--', sanitizePath(path)]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout; } @@ -1107,7 +1106,7 @@ export class Repository { } const args = ['diff', '--cached', '--', sanitizePath(path)]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout; } @@ -1120,13 +1119,13 @@ export class Repository { } const args = ['diff', '--cached', ref, '--', sanitizePath(path)]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout; } async diffBlobs(object1: string, object2: string): Promise { const args = ['diff', object1, object2]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout; } @@ -1140,7 +1139,7 @@ export class Repository { } const args = ['diff', range, '--', sanitizePath(path)]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout.trim(); } @@ -1155,7 +1154,7 @@ export class Repository { args.push(ref); } - const gitResult = await this.run(args); + const gitResult = await this.exec(args); if (gitResult.exitCode) { return []; } @@ -1228,14 +1227,14 @@ export class Repository { async getMergeBase(ref1: string, ref2: string): Promise { const args = ['merge-base', ref1, ref2]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout.trim(); } async hashObject(data: string): Promise { const args = ['hash-object', '-w', '--stdin']; - const result = await this.run(args, { input: data }); + const result = await this.exec(args, { input: data }); return result.stdout.trim(); } @@ -1251,10 +1250,10 @@ export class Repository { if (paths && paths.length) { for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { - await this.run([...args, '--', ...chunk]); + await this.exec([...args, '--', ...chunk]); } } else { - await this.run([...args, '--', '.']); + await this.exec([...args, '--', '.']); } } @@ -1267,7 +1266,7 @@ export class Repository { args.push(...paths.map(sanitizePath)); - await this.run(args); + await this.exec(args); } async stage(path: string, data: string): Promise { @@ -1300,7 +1299,7 @@ export class Repository { add = '--add'; } - await this.run(['update-index', add, '--cacheinfo', mode, hash, path]); + await this.exec(['update-index', add, '--cacheinfo', mode, hash, path]); } async checkout(treeish: string, paths: string[], opts: { track?: boolean, detached?: boolean } = Object.create(null)): Promise { @@ -1321,10 +1320,10 @@ export class Repository { try { if (paths && paths.length > 0) { for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { - await this.run([...args, '--', ...chunk]); + await this.exec([...args, '--', ...chunk]); } } else { - await this.run(args); + await this.exec(args); } } catch (err) { if (/Please,? commit your changes or stash them/.test(err.stderr || '')) { @@ -1375,21 +1374,21 @@ export class Repository { } try { - await this.run(args, !opts.amend || message ? { input: message || '' } : {}); + await this.exec(args, !opts.amend || message ? { input: message || '' } : {}); } catch (commitErr) { await this.handleCommitError(commitErr); } } async rebaseAbort(): Promise { - await this.run(['rebase', '--abort']); + await this.exec(['rebase', '--abort']); } async rebaseContinue(): Promise { const args = ['rebase', '--continue']; try { - await this.run(args); + await this.exec(args); } catch (commitErr) { await this.handleCommitError(commitErr); } @@ -1402,14 +1401,14 @@ export class Repository { } try { - await this.run(['config', '--get-all', 'user.name']); + await this.exec(['config', '--get-all', 'user.name']); } catch (err) { err.gitErrorCode = GitErrorCodes.NoUserNameConfigured; throw err; } try { - await this.run(['config', '--get-all', 'user.email']); + await this.exec(['config', '--get-all', 'user.email']); } catch (err) { err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured; throw err; @@ -1425,39 +1424,39 @@ export class Repository { args.push(ref); } - await this.run(args); + await this.exec(args); } async deleteBranch(name: string, force?: boolean): Promise { const args = ['branch', force ? '-D' : '-d', name]; - await this.run(args); + await this.exec(args); } async renameBranch(name: string): Promise { const args = ['branch', '-m', name]; - await this.run(args); + await this.exec(args); } async move(from: string, to: string): Promise { const args = ['mv', from, to]; - await this.run(args); + await this.exec(args); } async setBranchUpstream(name: string, upstream: string): Promise { const args = ['branch', '--set-upstream-to', upstream, name]; - await this.run(args); + await this.exec(args); } async deleteRef(ref: string): Promise { const args = ['update-ref', '-d', ref]; - await this.run(args); + await this.exec(args); } async merge(ref: string): Promise { const args = ['merge', ref]; try { - await this.run(args); + await this.exec(args); } catch (err) { if (/^CONFLICT /m.test(err.stdout || '')) { err.gitErrorCode = GitErrorCodes.Conflict; @@ -1476,12 +1475,12 @@ export class Repository { args = [...args, name]; } - await this.run(args); + await this.exec(args); } async deleteTag(name: string): Promise { let args = ['tag', '-d', name]; - await this.run(args); + await this.exec(args); } async clean(paths: string[]): Promise { @@ -1494,7 +1493,7 @@ export class Repository { for (const paths of groups) { for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { - promises.push(limiter.queue(() => this.run([...args, '--', ...chunk]))); + promises.push(limiter.queue(() => this.exec([...args, '--', ...chunk]))); } } @@ -1502,10 +1501,10 @@ export class Repository { } async undo(): Promise { - await this.run(['clean', '-fd']); + await this.exec(['clean', '-fd']); try { - await this.run(['checkout', '--', '.']); + await this.exec(['checkout', '--', '.']); } catch (err) { if (/did not match any file\(s\) known to git\./.test(err.stderr || '')) { return; @@ -1517,11 +1516,11 @@ export class Repository { async reset(treeish: string, hard: boolean = false): Promise { const args = ['reset', hard ? '--hard' : '--soft', treeish]; - await this.run(args); + await this.exec(args); } async revert(treeish: string, paths: string[]): Promise { - const result = await this.run(['branch']); + const result = await this.exec(['branch']); let args: string[]; // In case there are no branches, we must use rm --cached @@ -1534,10 +1533,10 @@ export class Repository { try { if (paths && paths.length > 0) { for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { - await this.run([...args, '--', ...chunk]); + await this.exec([...args, '--', ...chunk]); } } else { - await this.run([...args, '--', '.']); + await this.exec([...args, '--', '.']); } } catch (err) { // In case there are merge conflicts to be resolved, git reset will output @@ -1552,17 +1551,17 @@ export class Repository { async addRemote(name: string, url: string): Promise { const args = ['remote', 'add', name, url]; - await this.run(args); + await this.exec(args); } async removeRemote(name: string): Promise { const args = ['remote', 'remove', name]; - await this.run(args); + await this.exec(args); } async renameRemote(name: string, newName: string): Promise { const args = ['remote', 'rename', name, newName]; - await this.run(args); + await this.exec(args); } async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean, readonly cancellationToken?: CancellationToken } = {}): Promise { @@ -1595,7 +1594,7 @@ export class Repository { } try { - await this.run(args, spawnOptions); + await this.exec(args, spawnOptions); } catch (err) { if (/No remote repository specified\./.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified; @@ -1628,7 +1627,7 @@ export class Repository { } try { - await this.run(args, { + await this.exec(args, { cancellationToken: options.cancellationToken, env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } }); @@ -1658,7 +1657,7 @@ export class Repository { args.push(branch); try { - await this.run(args, options); + await this.exec(args, options); } catch (err) { if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout || '')) { err.gitErrorCode = GitErrorCodes.Conflict; @@ -1700,7 +1699,7 @@ export class Repository { } try { - await this.run(args, { env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } }); + await this.exec(args, { env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } }); } catch (err) { if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.PushRejected; @@ -1718,13 +1717,13 @@ export class Repository { async cherryPick(commitHash: string): Promise { const args = ['cherry-pick', commitHash]; - await this.run(args); + await this.exec(args); } async blame(path: string): Promise { try { const args = ['blame', sanitizePath(path)]; - const result = await this.run(args); + const result = await this.exec(args); return result.stdout.trim(); } catch (err) { if (/^fatal: no such path/.test(err.stderr || '')) { @@ -1747,7 +1746,7 @@ export class Repository { args.push('-m', message); } - await this.run(args); + await this.exec(args); } catch (err) { if (/No local changes to save/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoLocalChanges; @@ -1773,7 +1772,7 @@ export class Repository { args.push(`stash@{${index}}`); } - await this.run(args); + await this.exec(args); } catch (err) { if (/No stash found/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoStashFound; @@ -1795,7 +1794,7 @@ export class Repository { } try { - await this.run(args); + await this.exec(args); } catch (err) { if (/No stash found/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoStashFound; @@ -1860,7 +1859,7 @@ export class Repository { async getHEAD(): Promise { try { - const result = await this.run(['symbolic-ref', '--short', 'HEAD']); + const result = await this.exec(['symbolic-ref', '--short', 'HEAD']); if (!result.stdout) { throw new Error('Not in a branch'); @@ -1868,7 +1867,7 @@ export class Repository { return { name: result.stdout.trim(), commit: undefined, type: RefType.Head }; } catch (err) { - const result = await this.run(['rev-parse', 'HEAD']); + const result = await this.exec(['rev-parse', 'HEAD']); if (!result.stdout) { throw new Error('Error parsing HEAD'); @@ -1879,7 +1878,7 @@ export class Repository { } async findTrackingBranches(upstreamBranch: string): Promise { - const result = await this.run(['for-each-ref', '--format', '%(refname:short)%00%(upstream:short)', 'refs/heads']); + const result = await this.exec(['for-each-ref', '--format', '%(refname:short)%00%(upstream:short)', 'refs/heads']); return result.stdout.trim().split('\n') .map(line => line.trim().split('\0')) .filter(([_, upstream]) => upstream === upstreamBranch) @@ -1907,7 +1906,7 @@ export class Repository { args.push('--contains', opts.contains); } - const result = await this.run(args); + const result = await this.exec(args); const fn = (line: string): Ref | null => { let match: RegExpExecArray | null; @@ -1930,7 +1929,7 @@ export class Repository { } async getStashes(): Promise { - const result = await this.run(['stash', 'list']); + const result = await this.exec(['stash', 'list']); const regex = /^stash@{(\d+)}:(.+)$/; const rawStashes = result.stdout.trim().split('\n') .filter(b => !!b) @@ -1942,7 +1941,7 @@ export class Repository { } async getRemotes(): Promise { - const result = await this.run(['remote', '--verbose']); + const result = await this.exec(['remote', '--verbose']); const lines = result.stdout.trim().split('\n').filter(l => !!l); const remotes: MutableRemote[] = []; @@ -1985,7 +1984,7 @@ export class Repository { args.push(`refs/heads/${name}`, `refs/remotes/${name}`); } - const result = await this.run(args); + const result = await this.exec(args); const branches: Branch[] = result.stdout.trim().split('\n').map(line => { let [branchName, upstream, status, ref] = line.trim().split('\0'); @@ -2067,7 +2066,7 @@ export class Repository { async getCommitTemplate(): Promise { try { - const result = await this.run(['config', '--get', 'commit.template']); + const result = await this.exec(['config', '--get', 'commit.template']); if (!result.stdout) { return ''; @@ -2090,7 +2089,7 @@ export class Repository { } async getCommit(ref: string): Promise { - const result = await this.run(['show', '-s', `--format=${COMMIT_FORMAT}`, '-z', ref]); + const result = await this.exec(['show', '-s', `--format=${COMMIT_FORMAT}`, '-z', ref]); const commits = parseGitCommits(result.stdout); if (commits.length === 0) { return Promise.reject('bad commit format'); @@ -2102,7 +2101,7 @@ export class Repository { const args = ['submodule', 'update']; for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { - await this.run([...args, '--', ...chunk]); + await this.exec([...args, '--', ...chunk]); } } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e85a4f16921..c374e780faf 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1482,7 +1482,7 @@ export class Repository implements Disposable { const maybeRebased = await this.run(Operation.Log, async () => { try { - const result = await this.repository.run(['log', '--oneline', '--cherry', `${currentBranch ?? ''}...${currentBranch ?? ''}@{upstream}`, '--']); + const result = await this.repository.exec(['log', '--oneline', '--cherry', `${currentBranch ?? ''}...${currentBranch ?? ''}@{upstream}`, '--']); if (result.exitCode) { return false; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index 1d62a12c170..f9f971bdbda 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -7,9 +7,12 @@ import 'mocha'; import * as assert from 'assert'; import { join } from 'path'; import { commands, workspace, window, Uri, Range, Position, ViewColumn } from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - commands', () => { + teardown(assertNoRpc); + test('getCommands', function (done) { let p1 = commands.getCommands().then(commands => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts index 0b6f13fb01f..0a9ddf7aa2d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts @@ -6,9 +6,12 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - configuration', () => { + teardown(assertNoRpc); + test('configurations, language defaults', function () { const defaultLanguageSettings = vscode.workspace.getConfiguration().get('[abcLang]'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index ec976ff5334..145c2ee3bce 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -5,11 +5,13 @@ import * as assert from 'assert'; import { debug, workspace, Disposable, commands, window } from 'vscode'; -import { disposeAll } from '../utils'; +import { assertNoRpc, disposeAll } from '../utils'; import { basename } from 'path'; suite('vscode API - debug', function () { + teardown(assertNoRpc); + test('breakpoints', async function () { assert.equal(debug.breakpoints.length, 0); let onDidChangeBreakpointsCounter = 0; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index b5a0a79166b..ef16bbe1c61 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -5,11 +5,14 @@ import * as assert from 'assert'; import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection, Uri, env } from 'vscode'; -import { createRandomFile, deleteFile, closeAllEditors } from '../utils'; +import { createRandomFile, deleteFile, closeAllEditors, assertNoRpc } from '../utils'; suite('vscode API - editors', () => { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); function withRandomFileEditor(initialContents: string, run: (editor: TextEditor, doc: TextDocument) => Thenable): Thenable { return createRandomFile(initialContents).then(file => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index 7bc2f75973f..e3f0106fe10 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { env, extensions, ExtensionKind, UIKind, Uri } from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - env', () => { + teardown(assertNoRpc); + test('env is set', function () { assert.equal(typeof env.language, 'string'); assert.equal(typeof env.appRoot, 'string'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts index a59841f6689..0553a4444db 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts @@ -6,10 +6,12 @@ import * as assert from 'assert'; import { join } from 'path'; import * as vscode from 'vscode'; -import { createRandomFile, testFs } from '../utils'; +import { assertNoRpc, createRandomFile, testFs } from '../utils'; suite('vscode API - languages', () => { + teardown(assertNoRpc); + const isWindows = process.platform === 'win32'; function positionToString(p: vscode.Position) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index 55fbe0655a7..701c947426b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { window, commands } from 'vscode'; -import { closeAllEditors } from '../utils'; +import { assertNoRpc, closeAllEditors } from '../utils'; interface QuickPickExpected { events: string[]; @@ -20,7 +20,10 @@ interface QuickPickExpected { suite('vscode API - quick input', function () { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('createQuickPick, select second', function (_done) { let done = (err?: any) => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts new file mode 100644 index 00000000000..2b336f09718 --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assertNoRpcFromEntry, assertNoRpc, disposeAll } from '../utils'; +import * as vscode from 'vscode'; + +suite('vscode', function () { + + const dispo: vscode.Disposable[] = []; + + teardown(() => { + assertNoRpc(); + disposeAll(dispo); + }); + + test('no rpc', function () { + assertNoRpc(); + }); + + test('no rpc, createTextEditorDecorationType(...)', function () { + const item = vscode.window.createTextEditorDecorationType({}); + dispo.push(item); + assertNoRpcFromEntry([item, 'TextEditorDecorationType']); + }); + + test('no rpc, createOutputChannel(...)', function () { + const item = vscode.window.createOutputChannel('hello'); + dispo.push(item); + assertNoRpcFromEntry([item, 'OutputChannel']); + }); + + test('no rpc, createDiagnosticCollection(...)', function () { + const item = vscode.languages.createDiagnosticCollection(); + dispo.push(item); + assertNoRpcFromEntry([item, 'DiagnosticCollection']); + }); + + test('no rpc, createQuickPick(...)', function () { + const item = vscode.window.createQuickPick(); + dispo.push(item); + assertNoRpcFromEntry([item, 'QuickPick']); + }); + + test('no rpc, createInputBox(...)', function () { + const item = vscode.window.createInputBox(); + dispo.push(item); + assertNoRpcFromEntry([item, 'InputBox']); + }); + + test('no rpc, createStatusBarItem(...)', function () { + const item = vscode.window.createStatusBarItem(); + dispo.push(item); + assertNoRpcFromEntry([item, 'StatusBarItem']); + }); + + test('no rpc, createSourceControl(...)', function () { + this.skip(); + const item = vscode.scm.createSourceControl('foo', 'Hello'); + dispo.push(item); + assertNoRpcFromEntry([item, 'SourceControl']); + }); + + test('no rpc, createCommentController(...)', function () { + this.skip(); + const item = vscode.comments.createCommentController('foo', 'Hello'); + dispo.push(item); + assertNoRpcFromEntry([item, 'CommentController']); + }); + + test('no rpc, createWebviewPanel(...)', function () { + const item = vscode.window.createWebviewPanel('webview', 'Hello', vscode.ViewColumn.Active); + dispo.push(item); + assertNoRpcFromEntry([item, 'WebviewPanel']); + }); + + test('no rpc, createTreeView(...)', function () { + const treeDataProvider = new class implements vscode.TreeDataProvider { + getTreeItem(element: string): vscode.TreeItem | Thenable { + return new vscode.TreeItem(element); + } + getChildren(_element?: string): vscode.ProviderResult { + return ['foo', 'bar']; + } + }; + const item = vscode.window.createTreeView('test.treeId', { treeDataProvider }); + dispo.push(item); + assertNoRpcFromEntry([item, 'TreeView']); + }); + + test('no rpc, createNotebookEditorDecorationType(...)', function () { + const item = vscode.notebook.createNotebookEditorDecorationType({ top: {} }); + dispo.push(item); + assertNoRpcFromEntry([item, 'NotebookEditorDecorationType']); + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 01232a99c8c..9cede7605b4 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -5,6 +5,7 @@ import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions } from 'vscode'; import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; +import { assertNoRpc } from '../utils'; // Disable terminal tests: // - Web https://github.com/microsoft/vscode/issues/92826 @@ -30,6 +31,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; let disposables: Disposable[] = []; teardown(() => { + assertNoRpc(); disposables.forEach(d => d.dispose()); disposables.length = 0; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts index 53265b35e99..7143b40fa0c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts @@ -6,9 +6,12 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; +import { assertNoRpc } from '../utils'; suite('vscode API - types', () => { + teardown(assertNoRpc); + test('static properties, es5 compat class', function () { assert.ok(vscode.ThemeIcon.File instanceof vscode.ThemeIcon); assert.ok(vscode.ThemeIcon.Folder instanceof vscode.ThemeIcon); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 83357278c5d..14947da78e2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import 'mocha'; import * as os from 'os'; import * as vscode from 'vscode'; -import { closeAllEditors, delay, disposeAll } from '../utils'; +import { assertNoRpc, closeAllEditors, delay, disposeAll } from '../utils'; const webviewId = 'myWebview'; @@ -26,8 +26,8 @@ suite.skip('vscode API - webview', () => { } teardown(async () => { + assertNoRpc(); await closeAllEditors(); - disposeAll(disposables); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 89f3dbb7259..65fd89d3b96 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -6,12 +6,15 @@ import * as assert from 'assert'; import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, QuickPickItem, TextEditor } from 'vscode'; import { join } from 'path'; -import { closeAllEditors, pathEquals, createRandomFile } from '../utils'; +import { closeAllEditors, pathEquals, createRandomFile, assertNoRpc } from '../utils'; suite('vscode API - window', () => { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('editor, active text editor', async () => { const doc = await workspace.openTextDocument(join(workspace.rootPath || '', './far.js')); @@ -429,8 +432,8 @@ suite('vscode API - window', () => { }); test('showQuickPick, select first two', async function () { - const label = 'showQuickPick, select first two'; - let i = 0; + // const label = 'showQuickPick, select first two'; + // let i = 0; const resolves: ((value: string) => void)[] = []; let done: () => void; const unexpected = new Promise((resolve, reject) => { @@ -442,26 +445,26 @@ suite('vscode API - window', () => { canPickMany: true }); const first = new Promise(resolve => resolves.push(resolve)); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await new Promise(resolve => setTimeout(resolve, 100)); // Allow UI to update. - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.quickOpenSelectNext'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); assert.equal(await first, 'eins'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.quickPickManyToggle'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); const second = new Promise(resolve => resolves.push(resolve)); await commands.executeCommand('workbench.action.quickOpenSelectNext'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); assert.equal(await second, 'zwei'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.quickPickManyToggle'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); assert.deepStrictEqual(await picks, ['eins', 'zwei']); - console.log(`${label}: ${++i}`); + // console.log(`${label}: ${++i}`); done!(); return unexpected; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts index e192c63c0ab..e3f0c13dd2d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts @@ -5,16 +5,15 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, withLogDisabled } from '../utils'; +import { assertNoRpc, createRandomFile, disposeAll, withLogDisabled } from '../utils'; suite('vscode API - workspace events', () => { const disposables: vscode.Disposable[] = []; teardown(() => { - for (const dispo of disposables) { - dispo.dispose(); - } + assertNoRpc(); + disposeAll(disposables); disposables.length = 0; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts index 2eb21a4c1f9..193ab54be61 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { posix } from 'path'; +import { assertNoRpc } from '../utils'; suite('vscode API - workspace-fs', () => { @@ -15,6 +16,8 @@ suite('vscode API - workspace-fs', () => { root = vscode.workspace.workspaceFolders![0]!.uri; }); + teardown(assertNoRpc); + test('fs.stat', async function () { const stat = await vscode.workspace.fs.stat(root); assert.equal(stat.type, vscode.FileType.Directory); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index c831e6d4306..d6761c73444 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event } from 'vscode'; +import { assertNoRpc } from '../utils'; // Disable tasks tests: // - Web https://github.com/microsoft/vscode/issues/90528 @@ -14,6 +15,7 @@ import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomEx let disposables: Disposable[] = []; teardown(() => { + assertNoRpc(); disposables.forEach(d => d.dispose()); disposables.length = 0; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index b3241e6d384..ace2479b343 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -5,14 +5,17 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled, revertAllDirty } from '../utils'; +import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled, revertAllDirty, assertNoRpc } from '../utils'; import { join, posix, basename } from 'path'; import * as fs from 'fs'; import { TestFS } from '../memfs'; suite('vscode API - workspace', () => { - teardown(closeAllEditors); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('MarkdownString', function () { let md = new vscode.MarkdownString(); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 392d0be8461..f7923bbc9f7 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -72,3 +72,48 @@ export function withLogDisabled(runnable: () => Promise): () => Promise) { + if (!obj) { + return; + } + if (typeof obj !== 'object' && typeof obj !== 'function') { + return; + } + if (seen.has(obj)) { + return; + } + seen.add(obj); + + if (obj[symProtocol]) { + rpcPaths.push(`PROTOCOL via ${path}`); + } + if (obj[symProxy]) { + proxyPaths.push(`PROXY '${obj[symProxy]}' via ${path}`); + } + + for (const key in obj) { + walk(obj[key], `${path}.${String(key)}`, seen); + } + } + + try { + walk(entry[0], entry[1], new Set()); + } catch (err) { + assert.fail(err); + } + assert.strictEqual(rpcPaths.length, 0, rpcPaths.join('\n')); + assert.strictEqual(proxyPaths.length, 0, proxyPaths.join('\n')); // happens... +} diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index 52cc2ae8281..02a8488d51e 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -31,10 +31,12 @@ export interface CancellableEvent { stopPropagation(): void; } +export function stopEvent(event: T): T { + event.preventDefault(); + event.stopPropagation(); + return event; +} + export function stop(event: BaseEvent): BaseEvent { - return BaseEvent.map(event, e => { - e.preventDefault(); - e.stopPropagation(); - return e; - }); -} \ No newline at end of file + return BaseEvent.map(event, stopEvent); +} diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9bc49fdce70..d0ac4d8b996 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -64,6 +64,7 @@ export interface IListViewOptions extends IListViewOptionsUpdate { readonly mouseSupport?: boolean; readonly accessibilityProvider?: IListViewAccessibilityProvider; readonly transformOptimization?: boolean; + readonly alwaysConsumeMouseWheel?: boolean; } const DefaultOptions = { @@ -80,7 +81,8 @@ const DefaultOptions = { drop() { } }, horizontalScrolling: false, - transformOptimization: true + transformOptimization: true, + alwaysConsumeMouseWheel: true, }; export class ElementsDragAndDropData implements IDragAndDropData { @@ -327,6 +329,7 @@ export class ListView implements ISpliceable, IDisposable { this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb)); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { + alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel), horizontal: ScrollbarVisibility.Auto, vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows), diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index e530b43d36f..81caae42707 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -13,7 +13,7 @@ import { Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; +import { domEvent, stopEvent } from 'vs/base/browser/event'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list'; import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView'; import { Color } from 'vs/base/common/color'; @@ -854,6 +854,7 @@ export interface IListOptions { readonly additionalScrollHeight?: number; readonly transformOptimization?: boolean; readonly smoothScrolling?: boolean; + readonly alwaysConsumeMouseWheel?: boolean; } export interface IListStyles { @@ -1148,35 +1149,26 @@ export class List implements ISpliceable, IThemable, IDisposable { get onTouchStart(): Event> { return this.view.onTouchStart; } get onTap(): Event> { return this.view.onTap; } - private didJustPressContextMenuKey: boolean = false; @memoize get onContextMenu(): Event> { - const fromKeydown = Event.chain(domEvent(this.view.domNode, 'keydown')) + const fromKeyboard = Event.chain(domEvent(this.view.domNode, 'keyup')) .map(e => new StandardKeyboardEvent(e)) - .filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) - .filter(e => { e.preventDefault(); e.stopPropagation(); return false; }) - .event as Event; - - const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup')) - .filter(() => { - const didJustPressContextMenuKey = this.didJustPressContextMenuKey; - this.didJustPressContextMenuKey = false; - return didJustPressContextMenuKey; - }) - .filter(() => this.getFocus().length > 0 && !!this.view.domElement(this.getFocus()[0])) - .map(browserEvent => { - const index = this.getFocus()[0]; - const element = this.view.element(index); - const anchor = this.view.domElement(index) as HTMLElement; + .filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) + .map(stopEvent) + .map(({ browserEvent }) => { + const focus = this.getFocus(); + const index = focus.length ? focus[0] : undefined; + const element = typeof index !== 'undefined' ? this.view.element(index) : undefined; + const anchor = typeof index !== 'undefined' ? this.view.domElement(index) as HTMLElement : this.view.domNode; return { index, element, anchor, browserEvent }; }) .event; const fromMouse = Event.chain(this.view.onContextMenu) - .filter(() => !this.didJustPressContextMenuKey) + .filter(e => !(e.browserEvent.button === 0 && e.browserEvent.buttons === 0 && !e.browserEvent.ctrlKey && !e.browserEvent.altKey && !e.browserEvent.metaKey)) .map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent })) .event; - return Event.any>(fromKeydown, fromKeyup, fromMouse); + return Event.any>(fromKeyboard, fromMouse); } get onKeyDown(): Event { return domEvent(this.view.domNode, 'keydown'); } @@ -1380,7 +1372,7 @@ export class List implements ISpliceable, IThemable, IDisposable { } domFocus(): void { - this.view.domNode.focus(); + this.view.domNode.focus({ preventScroll: true }); } layout(height?: number, width?: number): void { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 184be17557b..884207922f2 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -103,7 +103,7 @@ export class SuggestWidget implements IDisposable { private _focusedItem?: CompletionItem; private _ignoreFocusEvents: boolean = false; private _completionModel?: CompletionModel; - private _cappedHeight?: { wanted: number, capped: number }; + private _cappedHeight?: { wanted: number; capped: number; }; private _explainMode: boolean = false; readonly element: ResizableHTMLElement; @@ -217,6 +217,7 @@ export class SuggestWidget implements IDisposable { getHeight: (_element: CompletionItem): number => this.getLayoutInfo().itemHeight, getTemplateId: (_element: CompletionItem): string => 'suggestion' }, [renderer], { + alwaysConsumeMouseWheel: true, useShadows: false, mouseSupport: false, accessibilityProvider: { diff --git a/src/vs/editor/standalone/common/monarch/monarchCompile.ts b/src/vs/editor/standalone/common/monarch/monarchCompile.ts index aa487e7e68a..1357baefea7 100644 --- a/src/vs/editor/standalone/common/monarch/monarchCompile.ts +++ b/src/vs/editor/standalone/common/monarch/monarchCompile.ts @@ -81,12 +81,21 @@ function createKeywordMatcher(arr: string[], caseInsensitive: boolean = false): /** * Compiles a regular expression string, adding the 'i' flag if 'ignoreCase' is set, and the 'u' flag if 'unicode' is set. * Also replaces @\w+ or sequences with the content of the specified attribute + * @\w+ replacement can be avoided by escaping `@` signs with another `@` sign. + * @example /@attr/ will be replaced with the value of lexer[attr] + * @example /@@text/ will not be replaced and will become /@text/. */ function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp { let n = 0; - while (str.indexOf('@') >= 0 && n < 5) { // at most 5 expansions - n++; - str = str.replace(/@(\w+)/g, function (s, attr?) { + let hadExpansion: boolean; + do { + hadExpansion = false; + str = str.replace(/(.|^)@(\w+)/g, function (s, charBeforeAtSign, attr?) { + if (charBeforeAtSign === '@') { + // do not expand @@ + return s; + } + hadExpansion = true; let sub = ''; if (typeof (lexer[attr]) === 'string') { sub = lexer[attr]; @@ -99,9 +108,13 @@ function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp { throw monarchCommon.createError(lexer, 'attribute reference \'' + attr + '\' must be a string, used at: ' + str); } } - return (monarchCommon.empty(sub) ? '' : '(?:' + sub + ')'); + return charBeforeAtSign + (monarchCommon.empty(sub) ? '' : '(?:' + sub + ')'); }); - } + n++; + } while (hadExpansion && n < 5); + + // handle escaped @@ + str = str.replace(/@@/g, '@'); let flags = (lexer.ignoreCase ? 'i' : '') + (lexer.unicode ? 'u' : ''); return new RegExp(str, flags); diff --git a/src/vs/editor/standalone/common/monarch/monarchTypes.ts b/src/vs/editor/standalone/common/monarch/monarchTypes.ts index 5e3a798c6d0..d424427b23c 100644 --- a/src/vs/editor/standalone/common/monarch/monarchTypes.ts +++ b/src/vs/editor/standalone/common/monarch/monarchTypes.ts @@ -46,6 +46,10 @@ export interface IMonarchLanguage { * Defaults to false */ includeLF?: boolean; + /** + * Other keys that can be referred to by the tokenizer. + */ + [key: string]: any; } /** diff --git a/src/vs/editor/standalone/test/monarch/monarch.test.ts b/src/vs/editor/standalone/test/monarch/monarch.test.ts index 61fcafe555e..fd57288fdca 100644 --- a/src/vs/editor/standalone/test/monarch/monarch.test.ts +++ b/src/vs/editor/standalone/test/monarch/monarch.test.ts @@ -19,6 +19,17 @@ suite('Monarch', () => { return new MonarchTokenizer(modeService, null!, languageId, compile(languageId, language)); } + function getTokens(tokenizer: MonarchTokenizer, lines: string[]): Token[][] { + const actualTokens: Token[][] = []; + let state = tokenizer.getInitialState(); + for (const line of lines) { + const result = tokenizer.tokenize(line, true, state, 0); + actualTokens.push(result.tokens); + state = result.endState; + } + return actualTokens; + } + test('Ensure @rematch and nextEmbedded can be used together in Monarch grammar', () => { const modeService = new ModeServiceImpl(); const innerModeRegistration = ModesRegistry.registerLanguage({ @@ -65,13 +76,7 @@ suite('Monarch', () => { `""")`, ]; - const actualTokens: Token[][] = []; - let state = tokenizer.getInitialState(); - for (const line of lines) { - const result = tokenizer.tokenize(line, true, state, 0); - actualTokens.push(result.tokens); - state = result.endState; - } + const actualTokens = getTokens(tokenizer, lines); assert.deepStrictEqual(actualTokens, [ [ @@ -140,13 +145,7 @@ suite('Monarch', () => { `But the line was empty. This line should not be commented.`, ]; - const actualTokens: Token[][] = []; - let state = tokenizer.getInitialState(); - for (const line of lines) { - const result = tokenizer.tokenize(line, true, state, 0); - actualTokens.push(result.tokens); - state = result.endState; - } + const actualTokens = getTokens(tokenizer, lines); assert.deepStrictEqual(actualTokens, [ [new Token(0, 'comment.test', 'test')], @@ -190,13 +189,7 @@ suite('Monarch', () => { `PRINT 2*3:*FX200, 3` ]; - const actualTokens: Token[][] = []; - let state = tokenizer.getInitialState(); - for (const line of lines) { - const result = tokenizer.tokenize(line, true, state, 0); - actualTokens.push(result.tokens); - state = result.endState; - } + const actualTokens = getTokens(tokenizer, lines); assert.deepStrictEqual(actualTokens, [ [ @@ -218,4 +211,57 @@ suite('Monarch', () => { ]); }); + test('issue #115662: monarchCompile function need an extra option which can control replacement', () => { + const modeService = new ModeServiceImpl(); + + const tokenizer1 = createMonarchTokenizer(modeService, 'test', { + ignoreCase: false, + uselessReplaceKey1: '@uselessReplaceKey2', + uselessReplaceKey2: '@uselessReplaceKey3', + uselessReplaceKey3: '@uselessReplaceKey4', + uselessReplaceKey4: '@uselessReplaceKey5', + uselessReplaceKey5: '@ham' || '', + tokenizer: { + root: [ + { + regex: /@\w+/.test('@ham') + ? new RegExp(`^${'@uselessReplaceKey1'}$`) + : new RegExp(`^${'@ham'}$`), + action: { token: 'ham' } + }, + ], + }, + }); + + const tokenizer2 = createMonarchTokenizer(modeService, 'test', { + ignoreCase: false, + tokenizer: { + root: [ + { + regex: /@@ham/, + action: { token: 'ham' } + }, + ], + }, + }); + + const lines = [ + `@ham` + ]; + + const actualTokens1 = getTokens(tokenizer1, lines); + assert.deepStrictEqual(actualTokens1, [ + [ + new Token(0, 'ham.test', 'test'), + ] + ]); + + const actualTokens2 = getTokens(tokenizer2, lines); + assert.deepStrictEqual(actualTokens2, [ + [ + new Token(0, 'ham.test', 'test'), + ] + ]); + }); + }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 227452ee59b..49442b1ad96 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6505,6 +6505,10 @@ declare namespace monaco.languages { * Defaults to false */ includeLF?: boolean; + /** + * Other keys that can be referred to by the tokenizer. + */ + [key: string]: any; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ee5d812bc69..f48e12c11c3 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1501,8 +1501,9 @@ declare module 'vscode' { export interface WorkspaceEdit { replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; replaceNotebookCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; - replaceNotebookCellOutput(uri: Uri, index: number, outputs: (NotebookCellOutput | CellOutput)[], metadata?: WorkspaceEditEntryMetadata): void; replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCellOutput(uri: Uri, index: number, outputs: (NotebookCellOutput | CellOutput)[], metadata?: WorkspaceEditEntryMetadata): void; + appendNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void; } export interface NotebookEditorEdit { diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts index 156399cdf38..02076eb4492 100644 --- a/src/vs/workbench/api/browser/mainThreadCLICommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -20,7 +20,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { canExecuteOnWorkspace, getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IExtensionManifest } from 'vs/workbench/workbench.web.api'; @@ -100,8 +100,7 @@ class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean { if (!canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) { - const extensionKinds = getExtensionKind(manifest, this.productService, this.configurationService); - output.log(localize('cannot be installed', "Cannot install '{0}' extension because it is declared as {1} extension kind and does not run in this setup.", getExtensionId(manifest.publisher, manifest.name), extensionKinds.map(e => `"${e}"`).join(', '))); + output.log(localize('cannot be installed', "Cannot install the '{0}' extension because it is declared to not run in this setup.", getExtensionId(manifest.publisher, manifest.name))); return false; } return true; diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index f805b7f9cbe..37d4d9326d4 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -79,7 +79,6 @@ export class MainThreadTextEditorProperties { return { insertSpaces: modelOptions.insertSpaces, tabSize: modelOptions.tabSize, - indentSize: modelOptions.indentSize, cursorStyle: cursorStyle, lineNumbers: lineNumbers }; @@ -146,7 +145,6 @@ export class MainThreadTextEditorProperties { } return ( a.tabSize === b.tabSize - && a.indentSize === b.indentSize && a.insertSpaces === b.insertSpaces && a.cursorStyle === b.cursorStyle && a.lineNumbers === b.lineNumbers @@ -377,13 +375,6 @@ export class MainThreadTextEditor { if (typeof newConfiguration.tabSize !== 'undefined') { newOpts.tabSize = newConfiguration.tabSize; } - if (typeof newConfiguration.indentSize !== 'undefined') { - if (newConfiguration.indentSize === 'tabSize') { - newOpts.indentSize = newOpts.tabSize || creationOpts.tabSize; - } else { - newOpts.indentSize = newConfiguration.indentSize; - } - } this._model.updateOptions(newOpts); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2da02381a46..a21a1a708b0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -262,7 +262,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerDiffInformationCommand: (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => { checkProposedApiEnabled(extension); return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { - const activeTextEditor = extHostEditors.getActiveTextEditor(); + const activeTextEditor = extHostDocumentsAndEditors.activeEditor(true); if (!activeTextEditor) { extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; @@ -288,9 +288,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get appName() { return initData.environment.appName; }, get appRoot() { return initData.environment.appRoot?.fsPath ?? ''; }, get uriScheme() { return initData.environment.appUriScheme; }, - get clipboard(): vscode.Clipboard { - return extHostClipboard; - }, + get clipboard(): vscode.Clipboard { return extHostClipboard.value; }, get shell() { return extHostTerminalService.getDefaultShell(false, configProvider); }, @@ -831,7 +829,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostFileSystem.registerFileSystemProvider(extension.identifier, scheme, provider, options); }, get fs() { - return extHostConsumerFileSystem; + return extHostConsumerFileSystem.value; }, registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => { checkProposedApiEnabled(extension); @@ -1292,9 +1290,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I class Extension implements vscode.Extension { - private _extensionService: IExtHostExtensionService; - private _originExtensionId: ExtensionIdentifier; - private _identifier: ExtensionIdentifier; + #extensionService: IExtHostExtensionService; + #originExtensionId: ExtensionIdentifier; + #identifier: ExtensionIdentifier; readonly id: string; readonly extensionUri: URI; @@ -1303,9 +1301,9 @@ class Extension implements vscode.Extension { readonly extensionKind: vscode.ExtensionKind; constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { - this._extensionService = extensionService; - this._originExtensionId = originExtensionId; - this._identifier = description.identifier; + this.#extensionService = extensionService; + this.#originExtensionId = originExtensionId; + this.#identifier = description.identifier; this.id = description.identifier.value; this.extensionUri = description.extensionLocation; this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); @@ -1314,17 +1312,17 @@ class Extension implements vscode.Extension { } get isActive(): boolean { - return this._extensionService.isActivated(this._identifier); + return this.#extensionService.isActivated(this.#identifier); } get exports(): T { if (this.packageJSON.api === 'none') { return undefined!; // Strict nulloverride - Public api } - return this._extensionService.getExtensionExports(this._identifier); + return this.#extensionService.getExtensionExports(this.#identifier); } activate(): Thenable { - return this._extensionService.activateByIdWithErrors(this._identifier, { startup: false, extensionId: this._originExtensionId, activationEvent: 'api' }).then(() => this.exports); + return this.#extensionService.activateByIdWithErrors(this.#identifier, { startup: false, extensionId: this.#originExtensionId, activationEvent: 'api' }).then(() => this.exports); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index aa338df4341..3488d18f5e8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -237,7 +237,6 @@ export interface MainThreadDocumentsShape extends IDisposable { export interface ITextEditorConfigurationUpdate { tabSize?: number | 'auto'; - indentSize?: number | 'tabSize'; insertSpaces?: boolean | 'auto'; cursorStyle?: TextEditorCursorStyle; lineNumbers?: RenderLineNumbersType; @@ -245,7 +244,6 @@ export interface ITextEditorConfigurationUpdate { export interface IResolvedTextEditorConfiguration { tabSize: number; - indentSize: number; insertSpaces: boolean; cursorStyle: TextEditorCursorStyle; lineNumbers: RenderLineNumbersType; diff --git a/src/vs/workbench/api/common/extHostClipboard.ts b/src/vs/workbench/api/common/extHostClipboard.ts index 2983f22bd5b..f8665dacbed 100644 --- a/src/vs/workbench/api/common/extHostClipboard.ts +++ b/src/vs/workbench/api/common/extHostClipboard.ts @@ -3,22 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMainContext, MainContext, MainThreadClipboardShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import type * as vscode from 'vscode'; -export class ExtHostClipboard implements vscode.Clipboard { +export class ExtHostClipboard { - private readonly _proxy: MainThreadClipboardShape; + readonly value: vscode.Clipboard; constructor(mainContext: IMainContext) { - this._proxy = mainContext.getProxy(MainContext.MainThreadClipboard); - } - - readText(): Promise { - return this._proxy.$readText(); - } - - writeText(value: string): Promise { - return this._proxy.$writeText(value); + const proxy = mainContext.getProxy(MainContext.MainThreadClipboard); + this.value = Object.freeze({ + readText() { + return proxy.$readText(); + }, + writeText(value: string) { + return proxy.$writeText(value); + } + }); } } diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 138e4950ae2..7db034cd81f 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -44,8 +44,8 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { createWebviewEditorInset(editor: vscode.TextEditor, line: number, height: number, options: vscode.WebviewOptions | undefined, extension: IExtensionDescription): vscode.WebviewEditorInset { let apiEditor: ExtHostTextEditor | undefined; - for (const candidate of this._editors.getVisibleTextEditors()) { - if (candidate === editor) { + for (const candidate of this._editors.getVisibleTextEditors(true)) { + if (candidate.value === editor) { apiEditor = candidate; break; } @@ -121,7 +121,7 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { } }; - this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation); + this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.value.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation); this._insets.set(handle, { editor, inset, onDidReceiveMessage }); return inset; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 05df7be725c..2790a5dda3f 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -89,7 +89,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } private _activeDebugConsole: ExtHostDebugConsole; - get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; } + get activeDebugConsole(): vscode.DebugConsole { return this._activeDebugConsole.value; } private _breakpoints: Map; private _breakpointEventsActive: boolean; @@ -911,20 +911,20 @@ export class ExtHostDebugSession implements vscode.DebugSession { } } -export class ExtHostDebugConsole implements vscode.DebugConsole { +export class ExtHostDebugConsole { - private _debugServiceProxy: MainThreadDebugServiceShape; + readonly value: vscode.DebugConsole; constructor(proxy: MainThreadDebugServiceShape) { - this._debugServiceProxy = proxy; - } - append(value: string): void { - this._debugServiceProxy.$appendDebugConsole(value); - } - - appendLine(value: string): void { - this.append(value + '\n'); + this.value = Object.freeze({ + append(value: string): void { + proxy.$appendDebugConsole(value); + }, + appendLine(value: string): void { + this.append(value + '\n'); + } + }); } } diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index e378b49a198..bd80952ee53 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -18,6 +18,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { Iterable } from 'vs/base/common/iterator'; +import { Lazy } from 'vs/base/common/lazy'; class Reference { private _count = 0; @@ -49,13 +50,13 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha private readonly _onDidAddDocuments = new Emitter(); private readonly _onDidRemoveDocuments = new Emitter(); - private readonly _onDidChangeVisibleTextEditors = new Emitter(); - private readonly _onDidChangeActiveTextEditor = new Emitter(); + private readonly _onDidChangeVisibleTextEditors = new Emitter(); + private readonly _onDidChangeActiveTextEditor = new Emitter(); readonly onDidAddDocuments: Event = this._onDidAddDocuments.event; readonly onDidRemoveDocuments: Event = this._onDidRemoveDocuments.event; - readonly onDidChangeVisibleTextEditors: Event = this._onDidChangeVisibleTextEditors.event; - readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; + readonly onDidChangeVisibleTextEditors: Event = this._onDidChangeVisibleTextEditors.event; + readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; constructor( @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService, @@ -135,7 +136,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha data.id, this._extHostRpc.getProxy(MainContext.MainThreadTextEditors), this._logService, - documentData, + new Lazy(() => documentData.document), data.selections.map(typeConverters.Selection.to), data.options, data.visibleRanges.map(range => typeConverters.Range.to(range)), @@ -162,7 +163,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha } if (delta.removedEditors || delta.addedEditors) { - this._onDidChangeVisibleTextEditors.fire(this.allEditors()); + this._onDidChangeVisibleTextEditors.fire(this.allEditors().map(editor => editor.value)); } if (delta.newActiveEditor !== undefined) { this._onDidChangeActiveTextEditor.fire(this.activeEditor()); @@ -181,11 +182,17 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha return this._editors.get(id); } - activeEditor(): ExtHostTextEditor | undefined { + activeEditor(): vscode.TextEditor | undefined; + activeEditor(internal: true): ExtHostTextEditor | undefined; + activeEditor(internal?: true): vscode.TextEditor | ExtHostTextEditor | undefined { if (!this._activeEditorId) { return undefined; + } + const editor = this._editors.get(this._activeEditorId); + if (internal) { + return editor; } else { - return this._editors.get(this._activeEditorId); + return editor?.value; } } diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index 5bbd185c775..46e331f4c83 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainThreadFileSystemShape, MainContext } from './extHost.protocol'; +import { MainContext } from './extHost.protocol'; import * as vscode from 'vscode'; import * as files from 'vs/platform/files/common/files'; import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; @@ -12,49 +12,51 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; -export class ExtHostConsumerFileSystem implements vscode.FileSystem { +export class ExtHostConsumerFileSystem { readonly _serviceBrand: undefined; - private readonly _proxy: MainThreadFileSystemShape; + readonly value: vscode.FileSystem; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, - @IExtHostFileSystemInfo private readonly _fileSystemInfo: IExtHostFileSystemInfo, + @IExtHostFileSystemInfo fileSystemInfo: IExtHostFileSystemInfo, ) { - this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); - } + const proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); - stat(uri: vscode.Uri): Promise { - return this._proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError); - } - readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { - return this._proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError); - } - createDirectory(uri: vscode.Uri): Promise { - return this._proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError); - } - async readFile(uri: vscode.Uri): Promise { - return this._proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError); - } - writeFile(uri: vscode.Uri, content: Uint8Array): Promise { - return this._proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError); - } - delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise { - return this._proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); - } - rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise { - return this._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); - } - copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { - return this._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); - } - isWritableFileSystem(scheme: string): boolean | undefined { - const capabilities = this._fileSystemInfo.getCapabilities(scheme); - if (typeof capabilities === 'number') { - return !(capabilities & files.FileSystemProviderCapabilities.Readonly); - } - return undefined; + this.value = Object.freeze({ + stat(uri: vscode.Uri): Promise { + return proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError); + }, + readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { + return proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError); + }, + createDirectory(uri: vscode.Uri): Promise { + return proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError); + }, + async readFile(uri: vscode.Uri): Promise { + return proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError); + }, + writeFile(uri: vscode.Uri, content: Uint8Array): Promise { + return proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError); + }, + delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise { + return proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + }, + rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise { + return proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + }, + copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { + return proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + }, + isWritableFileSystem(scheme: string): boolean | undefined { + const capabilities = fileSystemInfo.getCapabilities(scheme); + if (typeof capabilities === 'number') { + return !(capabilities & files.FileSystemProviderCapabilities.Readonly); + } + return undefined; + } + }); } private static _handleError(err: any): never { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index a77e61aac27..49f35f11f9d 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -191,21 +191,22 @@ async function withToken(cb: (token: CancellationToken) => any) { } } -export class NotebookEditorDecorationType implements vscode.NotebookEditorDecorationType { +export class NotebookEditorDecorationType { private static readonly _Keys = new IdGenerator('NotebookEditorDecorationType'); - private _proxy: MainThreadNotebookShape; - public key: string; + readonly value: vscode.NotebookEditorDecorationType; constructor(proxy: MainThreadNotebookShape, options: vscode.NotebookDecorationRenderOptions) { - this.key = NotebookEditorDecorationType._Keys.nextId(); - this._proxy = proxy; - this._proxy.$registerNotebookEditorDecorationType(this.key, typeConverters.NotebookDecorationRenderOptions.from(options)); - } + const key = NotebookEditorDecorationType._Keys.nextId(); + proxy.$registerNotebookEditorDecorationType(key, typeConverters.NotebookDecorationRenderOptions.from(options)); - public dispose(): void { - this._proxy.$removeNotebookEditorDecorationType(this.key); + this.value = { + key, + dispose() { + proxy.$removeNotebookEditorDecorationType(key); + } + }; } } @@ -367,7 +368,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { - return new NotebookEditorDecorationType(this._proxy, options); + return new NotebookEditorDecorationType(this._proxy, options).value; } async openNotebookDocument(uriComponents: UriComponents, viewType?: string): Promise { diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 2f2fd4cdbdb..26ccfb2fa37 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -233,6 +233,7 @@ class ExtHostQuickInput implements QuickInput { private static _nextId = 1; _id = ExtHostQuickPick._nextId++; + #proxy: MainThreadQuickOpenShape; private _title: string | undefined; private _steps: number | undefined; private _totalSteps: number | undefined; @@ -260,7 +261,8 @@ class ExtHostQuickInput implements QuickInput { this._onDidChangeValueEmitter ]; - constructor(protected _proxy: MainThreadQuickOpenShape, protected _extensionId: ExtensionIdentifier, private _onDidDispose: () => void) { + constructor(proxy: MainThreadQuickOpenShape, protected _extensionId: ExtensionIdentifier, private _onDidDispose: () => void) { + this.#proxy = proxy; } get title() { @@ -409,7 +411,7 @@ class ExtHostQuickInput implements QuickInput { this._updateTimeout = undefined; } this._onDidDispose(); - this._proxy.$dispose(this._id); + this.#proxy.$dispose(this._id); } protected update(properties: Record): void { @@ -437,7 +439,7 @@ class ExtHostQuickInput implements QuickInput { } private dispatchUpdate() { - this._proxy.$createOrUpdate(this._pendingUpdate); + this.#proxy.$createOrUpdate(this._pendingUpdate); this._pendingUpdate = { id: this._id }; } } diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index da040c1084b..ba9c5b184ae 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -18,6 +18,9 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { [['statusBarItem.errorBackground', new ThemeColor('statusBarItem.errorForeground')]] ); + #proxy: MainThreadStatusBarShape; + #commands: CommandsConverter; + private _id: number; private _alignment: number; private _priority?: number; @@ -38,14 +41,13 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { }; private _timeoutHandle: any; - private _proxy: MainThreadStatusBarShape; - private _commands: CommandsConverter; private _accessibilityInformation?: vscode.AccessibilityInformation; constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) { + this.#proxy = proxy; + this.#commands = commands; + this._id = ExtHostStatusBarEntry.ID_GEN++; - this._proxy = proxy; - this._commands = commands; this._statusId = id; this._statusName = name; this._alignment = alignment; @@ -122,12 +124,12 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { if (typeof command === 'string') { this._command = { fromApi: command, - internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration), + internal: this.#commands.toInternal({ title: '', command }, this._internalCommandRegistration), }; } else if (command) { this._command = { fromApi: command, - internal: this._commands.toInternal(command, this._internalCommandRegistration), + internal: this.#commands.toInternal(command, this._internalCommandRegistration), }; } else { this._command = undefined; @@ -148,7 +150,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { public hide(): void { clearTimeout(this._timeoutHandle); this._visible = false; - this._proxy.$dispose(this.id); + this.#proxy.$dispose(this.id); } private update(): void { @@ -169,7 +171,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { } // Set to status bar - this._proxy.$setEntry(this.id, this._statusId, this._statusName, this._text, this._tooltip, this._command?.internal, color, + this.#proxy.$setEntry(this.id, this._statusId, this._statusName, this._text, this._tooltip, this._command?.internal, color, this._backgroundColor, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, this._priority, this._accessibilityInformation); }, 0); diff --git a/src/vs/workbench/api/common/extHostStoragePaths.ts b/src/vs/workbench/api/common/extHostStoragePaths.ts index 34dc47ea635..45dc6abf5ed 100644 --- a/src/vs/workbench/api/common/extHostStoragePaths.ts +++ b/src/vs/workbench/api/common/extHostStoragePaths.ts @@ -48,7 +48,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { const storageUri = URI.joinPath(this._environment.workspaceStorageHome, storageName); try { - await this._extHostFileSystem.stat(storageUri); + await this._extHostFileSystem.value.stat(storageUri); this._logService.trace('[ExtHostStorage] storage dir already exists', storageUri); return storageUri; } catch { @@ -57,8 +57,8 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { try { this._logService.trace('[ExtHostStorage] creating dir and metadata-file', storageUri); - await this._extHostFileSystem.createDirectory(storageUri); - await this._extHostFileSystem.writeFile( + await this._extHostFileSystem.value.createDirectory(storageUri); + await this._extHostFileSystem.value.writeFile( URI.joinPath(storageUri, 'meta.json'), new TextEncoder().encode(JSON.stringify({ id: this._workspace.id, diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 9b0753a5709..35e824bad1d 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -341,7 +341,10 @@ export namespace TaskFilterDTO { class TaskExecutionImpl implements vscode.TaskExecution { - constructor(private readonly _tasks: ExtHostTaskBase, readonly _id: string, private readonly _task: vscode.Task) { + readonly #tasks: ExtHostTaskBase; + + constructor(tasks: ExtHostTaskBase, readonly _id: string, private readonly _task: vscode.Task) { + this.#tasks = tasks; } public get task(): vscode.Task { @@ -349,7 +352,7 @@ class TaskExecutionImpl implements vscode.TaskExecution { } public terminate(): void { - this._tasks.terminateTask(this); + this.#tasks.terminateTask(this); } public fireDidStartProcess(value: tasks.TaskProcessStartedDTO): void { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index f684dbb85b4..babcce145eb 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -47,7 +47,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); -export class ExtHostTerminal implements vscode.Terminal { +export class ExtHostTerminal { private _disposed: boolean = false; private _pidPromise: Promise; private _cols: number | undefined; @@ -57,6 +57,8 @@ export class ExtHostTerminal implements vscode.Terminal { public isOpen: boolean = false; + readonly value: vscode.Terminal; + constructor( private _proxy: MainThreadTerminalServiceShape, public _id: TerminalIdentifier, @@ -65,6 +67,49 @@ export class ExtHostTerminal implements vscode.Terminal { ) { this._creationOptions = Object.freeze(this._creationOptions); this._pidPromise = new Promise(c => this._pidPromiseComplete = c); + + const that = this; + this.value = { + get name(): string { + return that._name || ''; + }, + get processId(): Promise { + return that._pidPromise; + }, + get creationOptions(): Readonly { + return that._creationOptions; + }, + get exitStatus(): vscode.TerminalExitStatus | undefined { + return that._exitStatus; + }, + sendText(text: string, addNewLine: boolean = true): void { + that._checkDisposed(); + that._proxy.$sendText(that._id, text, addNewLine); + }, + show(preserveFocus: boolean): void { + that._checkDisposed(); + that._proxy.$show(that._id, preserveFocus); + }, + hide(): void { + that._checkDisposed(); + that._proxy.$hide(that._id); + }, + dispose(): void { + if (!that._disposed) { + that._disposed = true; + that._proxy.$dispose(that._id); + } + }, + get dimensions(): vscode.TerminalDimensions | undefined { + if (that._cols === undefined || that._rows === undefined) { + return undefined; + } + return { + columns: that._cols, + rows: that._rows + }; + } + }; } public async create( @@ -95,41 +140,16 @@ export class ExtHostTerminal implements vscode.Terminal { return this._id; } - public dispose(): void { - if (!this._disposed) { - this._disposed = true; - this._proxy.$dispose(this._id); - } - } - private _checkDisposed() { if (this._disposed) { throw new Error('Terminal has already been disposed'); } } - public get name(): string { - return this._name || ''; - } - public set name(name: string) { this._name = name; } - public get exitStatus(): vscode.TerminalExitStatus | undefined { - return this._exitStatus; - } - - public get dimensions(): vscode.TerminalDimensions | undefined { - if (this._cols === undefined || this._rows === undefined) { - return undefined; - } - return { - columns: this._cols, - rows: this._rows - }; - } - public setExitCode(code: number | undefined) { this._exitStatus = Object.freeze({ code }); } @@ -147,29 +167,6 @@ export class ExtHostTerminal implements vscode.Terminal { return true; } - public get processId(): Promise { - return this._pidPromise; - } - - public get creationOptions(): Readonly { - return this._creationOptions; - } - - public sendText(text: string, addNewLine: boolean = true): void { - this._checkDisposed(); - this._proxy.$sendText(this._id, text, addNewLine); - } - - public show(preserveFocus: boolean): void { - this._checkDisposed(); - this._proxy.$show(this._id, preserveFocus); - } - - public hide(): void { - this._checkDisposed(); - this._proxy.$hide(this._id); - } - public _setProcessId(processId: number | undefined): void { // The event may fire 2 times when the panel is restored if (this._pidPromiseComplete) { @@ -284,8 +281,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I private readonly _terminalLinkCache: Map> = new Map(); private readonly _terminalLinkCancellationSource: Map = new Map(); - public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } - public get terminals(): ExtHostTerminal[] { return this._terminals; } + public get activeTerminal(): vscode.Terminal | undefined { return this._activeTerminal?.value; } + public get terminals(): vscode.Terminal[] { return this._terminals.map(term => term.value); } protected readonly _onDidCloseTerminal: Emitter = new Emitter(); public get onDidCloseTerminal(): Event { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; } @@ -336,7 +333,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I this._terminalProcessDisposables[id] = disposable; }); this._terminals.push(terminal); - return terminal; + return terminal.value; } public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void { @@ -362,7 +359,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I if (terminal) { this._activeTerminal = terminal; if (original !== this._activeTerminal) { - this._onDidChangeActiveTerminal.fire(this._activeTerminal); + this._onDidChangeActiveTerminal.fire(this._activeTerminal.value); } } } @@ -370,7 +367,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public async $acceptTerminalProcessData(id: number, data: string): Promise { const terminal = this._getTerminalById(id); if (terminal) { - this._onDidWriteTerminalData.fire({ terminal, data }); + this._onDidWriteTerminalData.fire({ terminal: terminal.value, data }); } } @@ -379,8 +376,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I if (terminal) { if (terminal.setDimensions(cols, rows)) { this._onDidChangeTerminalDimensions.fire({ - terminal: terminal, - dimensions: terminal.dimensions as vscode.TerminalDimensions + terminal: terminal.value, + dimensions: terminal.value.dimensions as vscode.TerminalDimensions }); } } @@ -400,11 +397,11 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise { - const index = this._getTerminalObjectIndexById(this.terminals, id); + const index = this._getTerminalObjectIndexById(this._terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; terminal.setExitCode(exitCode); - this._onDidCloseTerminal.fire(terminal); + this._onDidCloseTerminal.fire(terminal.value); } } @@ -414,9 +411,9 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId); if (index !== null) { // The terminal has already been created (via createTerminal*), only fire the event - this.terminals[index]._id = id; + this._terminals[index]._id = id; this._onDidOpenTerminal.fire(this.terminals[index]); - this.terminals[index].isOpen = true; + this._terminals[index].isOpen = true; return; } } @@ -431,7 +428,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }; const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name); this._terminals.push(terminal); - this._onDidOpenTerminal.fire(terminal); + this._onDidOpenTerminal.fire(terminal.value); terminal.isOpen = true; } @@ -455,7 +452,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I await new Promise(r => { // Ensure open is called after onDidOpenTerminal const listener = this.onDidOpenTerminal(async e => { - if (e === terminal) { + if (e === terminal.value) { listener.dispose(); r(); } @@ -564,7 +561,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I this._terminalLinkCancellationSource.set(terminalId, cancellationSource); const result: ITerminalLinkDto[] = []; - const context: vscode.TerminalLinkContext = { terminal, line }; + const context: vscode.TerminalLinkContext = { terminal: terminal.value, line }; const promises: vscode.ProviderResult<{ provider: vscode.TerminalLinkProvider, links: vscode.TerminalLink[] }>[] = []; for (const provider of this._linkProviders) { diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 635a5c6c253..f3dff81c544 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -10,28 +10,29 @@ import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { IRange } from 'vs/editor/common/core/range'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { EndOfLine, Position, Range, Selection, SnippetString, TextEditorLineNumbersStyle, TextEditorRevealType } from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; import { ILogService } from 'vs/platform/log/common/log'; +import { Lazy } from 'vs/base/common/lazy'; -export class TextEditorDecorationType implements vscode.TextEditorDecorationType { +export class TextEditorDecorationType { private static readonly _Keys = new IdGenerator('TextEditorDecorationType'); - private _proxy: MainThreadTextEditorsShape; - public key: string; + readonly value: vscode.TextEditorDecorationType; constructor(proxy: MainThreadTextEditorsShape, options: vscode.DecorationRenderOptions) { - this.key = TextEditorDecorationType._Keys.nextId(); - this._proxy = proxy; - this._proxy.$registerTextEditorDecorationType(this.key, TypeConverters.DecorationRenderOptions.from(options)); + const key = TextEditorDecorationType._Keys.nextId(); + proxy.$registerTextEditorDecorationType(key, TypeConverters.DecorationRenderOptions.from(options)); + this.value = Object.freeze({ + key, + dispose() { + proxy.$removeTextEditorDecorationType(key); + } + }); } - public dispose(): void { - this._proxy.$removeTextEditorDecorationType(this.key); - } } export interface ITextEditOperation { @@ -134,36 +135,63 @@ export class TextEditorEdit { } } -export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { +export class ExtHostTextEditorOptions { private _proxy: MainThreadTextEditorsShape; private _id: string; private _logService: ILogService; private _tabSize!: number; - private _indentSize!: number; private _insertSpaces!: boolean; private _cursorStyle!: TextEditorCursorStyle; private _lineNumbers!: TextEditorLineNumbersStyle; + readonly value: vscode.TextEditorOptions; + constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration, logService: ILogService) { this._proxy = proxy; this._id = id; this._accept(source); this._logService = logService; + + const that = this; + + this.value = { + get tabSize(): number | string { + return that._tabSize; + }, + set tabSize(value: number | string) { + that._setTabSize(value); + }, + get insertSpaces(): boolean | string { + return that._insertSpaces; + }, + set insertSpaces(value: boolean | string) { + that._setInsertSpaces(value); + }, + get cursorStyle(): TextEditorCursorStyle { + return that._cursorStyle; + }, + set cursorStyle(value: TextEditorCursorStyle) { + that._setCursorStyle(value); + }, + get lineNumbers(): TextEditorLineNumbersStyle { + return that._lineNumbers; + }, + set lineNumbers(value: TextEditorLineNumbersStyle) { + that._setLineNumbers(value); + } + }; } public _accept(source: IResolvedTextEditorConfiguration): void { this._tabSize = source.tabSize; - this._indentSize = source.indentSize; this._insertSpaces = source.insertSpaces; this._cursorStyle = source.cursorStyle; this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers); } - public get tabSize(): number | string { - return this._tabSize; - } + // --- internal: tabSize private _validateTabSize(value: number | string): number | 'auto' | null { if (value === 'auto') { @@ -183,7 +211,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { return null; } - public set tabSize(value: number | string) { + private _setTabSize(value: number | string) { const tabSize = this._validateTabSize(value); if (tabSize === null) { // ignore invalid call @@ -202,50 +230,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { })); } - public get indentSize(): number | string { - return this._indentSize; - } - - private _validateIndentSize(value: number | string): number | 'tabSize' | null { - if (value === 'tabSize') { - return 'tabSize'; - } - if (typeof value === 'number') { - const r = Math.floor(value); - return (r > 0 ? r : null); - } - if (typeof value === 'string') { - const r = parseInt(value, 10); - if (isNaN(r)) { - return null; - } - return (r > 0 ? r : null); - } - return null; - } - - public set indentSize(value: number | string) { - const indentSize = this._validateIndentSize(value); - if (indentSize === null) { - // ignore invalid call - return; - } - if (typeof indentSize === 'number') { - if (this._indentSize === indentSize) { - // nothing to do - return; - } - // reflect the new indentSize value immediately - this._indentSize = indentSize; - } - this._warnOnError(this._proxy.$trySetOptions(this._id, { - indentSize: indentSize - })); - } - - public get insertSpaces(): boolean | string { - return this._insertSpaces; - } + // --- internal: insert spaces private _validateInsertSpaces(value: boolean | string): boolean | 'auto' { if (value === 'auto') { @@ -254,7 +239,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { return (value === 'false' ? false : Boolean(value)); } - public set insertSpaces(value: boolean | string) { + private _setInsertSpaces(value: boolean | string) { const insertSpaces = this._validateInsertSpaces(value); if (typeof insertSpaces === 'boolean') { if (this._insertSpaces === insertSpaces) { @@ -269,11 +254,9 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { })); } - public get cursorStyle(): TextEditorCursorStyle { - return this._cursorStyle; - } + // --- internal: cursor style - public set cursorStyle(value: TextEditorCursorStyle) { + private _setCursorStyle(value: TextEditorCursorStyle) { if (this._cursorStyle === value) { // nothing to do return; @@ -284,11 +267,9 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { })); } - public get lineNumbers(): TextEditorLineNumbersStyle { - return this._lineNumbers; - } + // --- internal: line number - public set lineNumbers(value: TextEditorLineNumbersStyle) { + private _setLineNumbers(value: TextEditorLineNumbersStyle) { if (this._lineNumbers === value) { // nothing to do return; @@ -368,31 +349,170 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { } } -export class ExtHostTextEditor implements vscode.TextEditor { - - private readonly _documentData: ExtHostDocumentData; +export class ExtHostTextEditor { private _selections: Selection[]; private _options: ExtHostTextEditorOptions; private _visibleRanges: Range[]; private _viewColumn: vscode.ViewColumn | undefined; private _disposed: boolean = false; - private _hasDecorationsForKey: { [key: string]: boolean; }; + private _hasDecorationsForKey = new Set(); + + readonly value: vscode.TextEditor; constructor( readonly id: string, private readonly _proxy: MainThreadTextEditorsShape, private readonly _logService: ILogService, - document: ExtHostDocumentData, + document: Lazy, selections: Selection[], options: IResolvedTextEditorConfiguration, visibleRanges: Range[], viewColumn: vscode.ViewColumn | undefined ) { - this._documentData = document; this._selections = selections; this._options = new ExtHostTextEditorOptions(this._proxy, this.id, options, _logService); this._visibleRanges = visibleRanges; this._viewColumn = viewColumn; - this._hasDecorationsForKey = Object.create(null); + + const that = this; + + this.value = Object.freeze({ + get document(): vscode.TextDocument { + return document.getValue(); + }, + set document(_value) { + throw readonly('document'); + }, + // --- selection + get selection(): Selection { + return that._selections && that._selections[0]; + }, + set selection(value: Selection) { + if (!(value instanceof Selection)) { + throw illegalArgument('selection'); + } + that._selections = [value]; + that._trySetSelection(); + }, + get selections(): Selection[] { + return that._selections; + }, + set selections(value: Selection[]) { + if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) { + throw illegalArgument('selections'); + } + that._selections = value; + that._trySetSelection(); + }, + // --- visible ranges + get visibleRanges(): Range[] { + return that._visibleRanges; + }, + set visibleRanges(_value: Range[]) { + throw readonly('visibleRanges'); + }, + // --- options + get options(): vscode.TextEditorOptions { + return that._options.value; + }, + set options(value: vscode.TextEditorOptions) { + if (!that._disposed) { + that._options.assign(value); + } + }, + // --- view column + get viewColumn(): vscode.ViewColumn | undefined { + return that._viewColumn; + }, + set viewColumn(_value) { + throw readonly('viewColumn'); + }, + // --- edit + edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise { + if (that._disposed) { + return Promise.reject(new Error('TextEditor#edit not possible on closed editors')); + } + const edit = new TextEditorEdit(document.getValue(), options); + callback(edit); + return that._applyEdit(edit); + }, + // --- snippet edit + insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise { + if (that._disposed) { + return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors')); + } + let ranges: IRange[]; + + if (!where || (Array.isArray(where) && where.length === 0)) { + ranges = that._selections.map(range => TypeConverters.Range.from(range)); + + } else if (where instanceof Position) { + const { lineNumber, column } = TypeConverters.Position.from(where); + ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }]; + + } else if (where instanceof Range) { + ranges = [TypeConverters.Range.from(where)]; + } else { + ranges = []; + for (const posOrRange of where) { + if (posOrRange instanceof Range) { + ranges.push(TypeConverters.Range.from(posOrRange)); + } else { + const { lineNumber, column } = TypeConverters.Position.from(posOrRange); + ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }); + } + } + } + return _proxy.$tryInsertSnippet(id, snippet.value, ranges, options); + }, + setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void { + const willBeEmpty = (ranges.length === 0); + if (willBeEmpty && !that._hasDecorationsForKey.has(decorationType.key)) { + // avoid no-op call to the renderer + return; + } + if (willBeEmpty) { + that._hasDecorationsForKey.delete(decorationType.key); + } else { + that._hasDecorationsForKey.add(decorationType.key); + } + that._runOnProxy(() => { + if (TypeConverters.isDecorationOptionsArr(ranges)) { + return _proxy.$trySetDecorations( + id, + decorationType.key, + TypeConverters.fromRangeOrRangeWithMessage(ranges) + ); + } else { + const _ranges: number[] = new Array(4 * ranges.length); + for (let i = 0, len = ranges.length; i < len; i++) { + const range = ranges[i]; + _ranges[4 * i] = range.start.line + 1; + _ranges[4 * i + 1] = range.start.character + 1; + _ranges[4 * i + 2] = range.end.line + 1; + _ranges[4 * i + 3] = range.end.character + 1; + } + return _proxy.$trySetDecorationsFast( + id, + decorationType.key, + _ranges + ); + } + }); + }, + revealRange(range: Range, revealType: vscode.TextEditorRevealType): void { + that._runOnProxy(() => _proxy.$tryRevealRange( + id, + TypeConverters.Range.from(range), + (revealType || TextEditorRevealType.Default) + )); + }, + show(column: vscode.ViewColumn) { + _proxy.$tryShowEditor(id, TypeConverters.ViewColumn.from(column)); + }, + hide() { + _proxy.$tryHideEditor(id); + } + }); } dispose() { @@ -400,164 +520,32 @@ export class ExtHostTextEditor implements vscode.TextEditor { this._disposed = true; } - show(column: vscode.ViewColumn) { - this._proxy.$tryShowEditor(this.id, TypeConverters.ViewColumn.from(column)); - } - - hide() { - this._proxy.$tryHideEditor(this.id); - } - - // ---- the document - - get document(): vscode.TextDocument { - return this._documentData.document; - } - - set document(value) { - throw readonly('document'); - } - - // ---- options - - get options(): vscode.TextEditorOptions { - return this._options; - } - - set options(value: vscode.TextEditorOptions) { - if (!this._disposed) { - this._options.assign(value); - } - } + // --- incoming: extension host MUST accept what the renderer says _acceptOptions(options: IResolvedTextEditorConfiguration): void { ok(!this._disposed); this._options._accept(options); } - // ---- visible ranges - - get visibleRanges(): Range[] { - return this._visibleRanges; - } - - set visibleRanges(value: Range[]) { - throw readonly('visibleRanges'); - } - _acceptVisibleRanges(value: Range[]): void { ok(!this._disposed); this._visibleRanges = value; } - // ---- view column - - get viewColumn(): vscode.ViewColumn | undefined { - return this._viewColumn; - } - - set viewColumn(value) { - throw readonly('viewColumn'); - } - _acceptViewColumn(value: vscode.ViewColumn) { ok(!this._disposed); this._viewColumn = value; } - // ---- selections - - get selection(): Selection { - return this._selections && this._selections[0]; - } - - set selection(value: Selection) { - if (!(value instanceof Selection)) { - throw illegalArgument('selection'); - } - this._selections = [value]; - this._trySetSelection(); - } - - get selections(): Selection[] { - return this._selections; - } - - set selections(value: Selection[]) { - if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) { - throw illegalArgument('selections'); - } - this._selections = value; - this._trySetSelection(); - } - - setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void { - const willBeEmpty = (ranges.length === 0); - if (willBeEmpty && !this._hasDecorationsForKey[decorationType.key]) { - // avoid no-op call to the renderer - return; - } - if (willBeEmpty) { - delete this._hasDecorationsForKey[decorationType.key]; - } else { - this._hasDecorationsForKey[decorationType.key] = true; - } - this._runOnProxy( - () => { - if (TypeConverters.isDecorationOptionsArr(ranges)) { - return this._proxy.$trySetDecorations( - this.id, - decorationType.key, - TypeConverters.fromRangeOrRangeWithMessage(ranges) - ); - } else { - const _ranges: number[] = new Array(4 * ranges.length); - for (let i = 0, len = ranges.length; i < len; i++) { - const range = ranges[i]; - _ranges[4 * i] = range.start.line + 1; - _ranges[4 * i + 1] = range.start.character + 1; - _ranges[4 * i + 2] = range.end.line + 1; - _ranges[4 * i + 3] = range.end.character + 1; - } - return this._proxy.$trySetDecorationsFast( - this.id, - decorationType.key, - _ranges - ); - } - } - ); - } - - revealRange(range: Range, revealType: vscode.TextEditorRevealType): void { - this._runOnProxy( - () => this._proxy.$tryRevealRange( - this.id, - TypeConverters.Range.from(range), - (revealType || TextEditorRevealType.Default) - ) - ); - } - - private _trySetSelection(): Promise { - const selection = this._selections.map(TypeConverters.Selection.from); - return this._runOnProxy(() => this._proxy.$trySetSelections(this.id, selection)); - } - _acceptSelections(selections: Selection[]): void { ok(!this._disposed); this._selections = selections; } - // ---- editing - - edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise { - if (this._disposed) { - return Promise.reject(new Error('TextEditor#edit not possible on closed editors')); - } - const edit = new TextEditorEdit(this._documentData.document, options); - callback(edit); - return this._applyEdit(edit); + private async _trySetSelection(): Promise { + const selection = this._selections.map(TypeConverters.Selection.from); + await this._runOnProxy(() => this._proxy.$trySetSelections(this.id, selection)); + return this.value; } private _applyEdit(editBuilder: TextEditorEdit): Promise { @@ -613,44 +601,12 @@ export class ExtHostTextEditor implements vscode.TextEditor { undoStopAfter: editData.undoStopAfter }); } - - insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise { - if (this._disposed) { - return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors')); - } - let ranges: IRange[]; - - if (!where || (Array.isArray(where) && where.length === 0)) { - ranges = this._selections.map(range => TypeConverters.Range.from(range)); - - } else if (where instanceof Position) { - const { lineNumber, column } = TypeConverters.Position.from(where); - ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }]; - - } else if (where instanceof Range) { - ranges = [TypeConverters.Range.from(where)]; - } else { - ranges = []; - for (const posOrRange of where) { - if (posOrRange instanceof Range) { - ranges.push(TypeConverters.Range.from(posOrRange)); - } else { - const { lineNumber, column } = TypeConverters.Position.from(posOrRange); - ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }); - } - } - } - - return this._proxy.$tryInsertSnippet(this.id, snippet.value, ranges, options); - } - - // ---- util - private _runOnProxy(callback: () => Promise): Promise { if (this._disposed) { this._logService.warn('TextEditor is closed/disposed'); return Promise.resolve(undefined); } + return callback().then(() => this, err => { if (!(err instanceof Error && err.name === 'DISPOSED')) { this._logService.warn(err); @@ -659,4 +615,3 @@ export class ExtHostTextEditor implements vscode.TextEditor { }); } } - diff --git a/src/vs/workbench/api/common/extHostTextEditors.ts b/src/vs/workbench/api/common/extHostTextEditors.ts index 3e95b2f382e..dc8bd7bd910 100644 --- a/src/vs/workbench/api/common/extHostTextEditors.ts +++ b/src/vs/workbench/api/common/extHostTextEditors.ts @@ -41,12 +41,17 @@ export class ExtHostEditors implements ExtHostEditorsShape { this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e)); } - getActiveTextEditor(): ExtHostTextEditor | undefined { + getActiveTextEditor(): vscode.TextEditor | undefined { return this._extHostDocumentsAndEditors.activeEditor(); } - getVisibleTextEditors(): vscode.TextEditor[] { - return this._extHostDocumentsAndEditors.allEditors(); + getVisibleTextEditors(): vscode.TextEditor[]; + getVisibleTextEditors(internal: true): ExtHostTextEditor[]; + getVisibleTextEditors(internal?: true): ExtHostTextEditor[] | vscode.TextEditor[] { + const editors = this._extHostDocumentsAndEditors.allEditors(); + return internal + ? editors + : editors.map(editor => editor.value); } showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): Promise; @@ -75,7 +80,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { const editorId = await this._proxy.$tryShowTextDocument(document.uri, options); const editor = editorId && this._extHostDocumentsAndEditors.getEditor(editorId); if (editor) { - return editor; + return editor.value; } // we have no editor... having an id means that we had an editor // on the main side and that it isn't the current editor anymore... @@ -87,7 +92,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { } createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType { - return new TextEditorDecorationType(this._proxy, options); + return new TextEditorDecorationType(this._proxy, options).value; } // --- called from main thread @@ -114,7 +119,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { // (2) fire change events if (data.options) { this._onDidChangeTextEditorOptions.fire({ - textEditor: textEditor, + textEditor: textEditor.value, options: { ...data.options, lineNumbers: TypeConverters.TextEditorLineNumbersStyle.to(data.options.lineNumbers) } }); } @@ -122,7 +127,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { const kind = TextEditorSelectionChangeKind.fromValue(data.selections.source); const selections = data.selections.selections.map(TypeConverters.Selection.to); this._onDidChangeTextEditorSelection.fire({ - textEditor, + textEditor: textEditor.value, selections, kind }); @@ -130,7 +135,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { if (data.visibleRanges) { const visibleRanges = arrays.coalesce(data.visibleRanges.map(TypeConverters.Range.to)); this._onDidChangeTextEditorVisibleRanges.fire({ - textEditor, + textEditor: textEditor.value, visibleRanges }); } @@ -143,9 +148,9 @@ export class ExtHostEditors implements ExtHostEditorsShape { throw new Error('Unknown text editor'); } const viewColumn = TypeConverters.ViewColumn.to(data[id]); - if (textEditor.viewColumn !== viewColumn) { + if (textEditor.value.viewColumn !== viewColumn) { textEditor._acceptViewColumn(viewColumn); - this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn }); + this._onDidChangeTextEditorViewColumn.fire({ textEditor: textEditor.value, viewColumn }); } } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5d1bb34cb8c..9c08ed54186 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { addIdToOutput, CellEditType, ICellEditOperation, ICellOutputEdit, IDisplayOutput, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; function es5ClassCompat(target: Function): any { @@ -648,17 +648,27 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } replaceNotebookCellOutput(uri: URI, index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ - _type: FileEditType.Cell, metadata, uri, edit: { - editType: CellEditType.Output, index, outputs: outputs.map(output => { - if (NotebookCellOutput.isNotebookCellOutput(output)) { - return addIdToOutput(output.toJSON()); - } else { - return addIdToOutput(output); - } - }) - } - }); + this._editNotebookCellOutput(uri, index, false, outputs, metadata); + } + + appendNotebookCellOutput(uri: URI, index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + this._editNotebookCellOutput(uri, index, true, outputs, metadata); + } + + private _editNotebookCellOutput(uri: URI, index: number, append: boolean, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { + const edit: ICellOutputEdit = { + editType: CellEditType.Output, + index, + append, + outputs: outputs.map(output => { + if (NotebookCellOutput.isNotebookCellOutput(output)) { + return addIdToOutput(output.toJSON()); + } else { + return addIdToOutput(output); + } + }) + }; + this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit }); } replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index adad271b38f..53616f44ba3 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -60,7 +60,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name); this._terminals.push(terminal); terminal.create(shellPath, shellArgs); - return terminal; + return terminal.value; } public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal { @@ -75,7 +75,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { withNullAsUndefined(options.strictEnv), withNullAsUndefined(options.hideFromUser), withNullAsUndefined(isFeatureTerminal)); - return terminal; + return terminal.value; } public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string { diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index cc1a211f6db..a76607f2e8e 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -11,7 +11,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ContextKeyDefinedExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { isString } from 'vs/base/common/types'; -import { MenuId, registerAction2, Action2, MenuRegistry, ICommandActionTitle } from 'vs/platform/actions/common/actions'; +import { MenuId, registerAction2, Action2, MenuRegistry, ICommandActionTitle, ILocalizedString } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -140,68 +140,7 @@ export class ViewsService extends Disposable implements IViewsService { this.onViewDescriptorsAdded(added, viewContainer); this.onViewDescriptorsRemoved(removed); })); - - // Register Action to Open View Container - if (viewContainer.commandActionDescriptor !== false) { - let { id, title, mnemonicTitle, keybindings, order } = viewContainer.commandActionDescriptor ?? { id: viewContainer.id }; - title = title ?? viewContainer.title; - const that = this; - this._register(registerAction2(class OpenViewContainerAction extends Action2 { - constructor() { - super({ - id, - get title(): ICommandActionTitle { - const viewContainerLocation = that.viewDescriptorService.getViewContainerLocation(viewContainer); - if (viewContainerLocation === ViewContainerLocation.Sidebar) { - return { value: localize('show view', "Show {0}", title), original: `Show ${title}` }; - } else { - return { value: localize('toggle view', "Toggle {0}", title), original: `Toggle ${title}` }; - } - }, - category: CATEGORIES.View.value, - precondition: ContextKeyExpr.has(getEnabledViewContainerContextKey(viewContainer.id)), - keybinding: keybindings ? { ...keybindings, weight: KeybindingWeight.WorkbenchContrib } : undefined, - f1: true - }); - } - public async run(serviceAccessor: ServicesAccessor): Promise { - const editorGroupService = serviceAccessor.get(IEditorGroupsService); - const viewDescriptorService = serviceAccessor.get(IViewDescriptorService); - const layoutService = serviceAccessor.get(IWorkbenchLayoutService); - const viewsService = serviceAccessor.get(IViewsService); - const viewContainerLocation = viewDescriptorService.getViewContainerLocation(viewContainer); - switch (viewContainerLocation) { - case ViewContainerLocation.Sidebar: - if (!viewsService.isViewContainerVisible(viewContainer.id) || !layoutService.hasFocus(Parts.SIDEBAR_PART)) { - await viewsService.openViewContainer(viewContainer.id, true); - } else { - editorGroupService.activeGroup.focus(); - } - break; - case ViewContainerLocation.Panel: - if (!viewsService.isViewContainerVisible(viewContainer.id) || !layoutService.hasFocus(Parts.PANEL_PART)) { - await viewsService.openViewContainer(viewContainer.id, true); - } else { - viewsService.closeViewContainer(viewContainer.id); - } - break; - } - } - })); - - if (mnemonicTitle) { - const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer); - this._register(MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - command: { - id, - title: mnemonicTitle, - }, - group: defaultLocation === ViewContainerLocation.Sidebar ? '3_views' : '4_panels', - when: ContextKeyExpr.has(getEnabledViewContainerContextKey(viewContainer.id)), - order: order ?? Number.MAX_VALUE - })); - } - } + this._register(this.registerOpenViewContainerAction(viewContainer)); } private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { @@ -218,130 +157,9 @@ export class ViewsService extends Disposable implements IViewsService { const composite = this.getComposite(container.id, location); for (const viewDescriptor of views) { const disposables = new DisposableStore(); - - // Register Action to Open View - if (viewDescriptor.commandActionDescriptor) { - const title = viewDescriptor.commandActionDescriptor.title ?? viewDescriptor.name; - const commandId = viewDescriptor.commandActionDescriptor.id; - const that = this; - disposables.add(registerAction2(class OpenViewAction extends Action2 { - constructor() { - super({ - id: commandId, - get title(): ICommandActionTitle { - const viewContainerLocation = that.viewDescriptorService.getViewLocationById(viewDescriptor.id); - if (viewContainerLocation === ViewContainerLocation.Sidebar) { - return { value: localize('show view', "Show {0}", title), original: `Show ${title}` }; - } else { - return { value: localize('toggle view', "Toggle {0}", title), original: `Toggle ${title}` }; - } - }, - category: CATEGORIES.View.value, - precondition: ContextKeyDefinedExpr.create(`${viewDescriptor.id}.active`), - keybinding: viewDescriptor.commandActionDescriptor!.keybindings ? { ...viewDescriptor.commandActionDescriptor!.keybindings, weight: KeybindingWeight.WorkbenchContrib } : undefined, - f1: true - }); - } - public async run(serviceAccessor: ServicesAccessor): Promise { - const editorGroupService = serviceAccessor.get(IEditorGroupsService); - const viewDescriptorService = serviceAccessor.get(IViewDescriptorService); - const layoutService = serviceAccessor.get(IWorkbenchLayoutService); - const viewsService = serviceAccessor.get(IViewsService); - const contextKeyService = serviceAccessor.get(IContextKeyService); - - const focusedViewId = FocusedViewContext.getValue(contextKeyService); - if (focusedViewId === viewDescriptor.id) { - if (viewDescriptorService.getViewLocationById(viewDescriptor.id) === ViewContainerLocation.Sidebar) { - editorGroupService.activeGroup.focus(); - } else { - layoutService.setPanelHidden(true); - } - } else { - viewsService.openView(viewDescriptor.id, true); - } - } - })); - - if (viewDescriptor.commandActionDescriptor.mnemonicTitle) { - const defaultViewContainer = this.viewDescriptorService.getDefaultContainerById(viewDescriptor.id); - if (defaultViewContainer) { - const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(defaultViewContainer); - disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - command: { - id: commandId, - title: viewDescriptor.commandActionDescriptor.mnemonicTitle, - }, - group: defaultLocation === ViewContainerLocation.Sidebar ? '3_views' : '4_panels', - when: ContextKeyDefinedExpr.create(`${viewDescriptor.id}.active`), - order: viewDescriptor.commandActionDescriptor.order ?? Number.MAX_VALUE - })); - } - } - } - - disposables.add(registerAction2(class FocusViewAction extends Action2 { - constructor() { - super({ - id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name) }, - category: composite ? composite.name : CATEGORIES.View, - menu: [{ - id: MenuId.CommandPalette, - when: viewDescriptor.when, - }], - keybinding: { - when: ContextKeyExpr.has(`${viewDescriptor.id}.active`), - weight: KeybindingWeight.WorkbenchContrib, - primary: viewDescriptor.focusCommand?.keybindings?.primary, - secondary: viewDescriptor.focusCommand?.keybindings?.secondary, - linux: viewDescriptor.focusCommand?.keybindings?.linux, - mac: viewDescriptor.focusCommand?.keybindings?.mac, - win: viewDescriptor.focusCommand?.keybindings?.win - } - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IViewsService).openView(viewDescriptor.id, true); - } - })); - - disposables.add(registerAction2(class ResetViewLocationAction extends Action2 { - constructor() { - super({ - id: `${viewDescriptor.id}.resetViewLocation`, - title: { - original: 'Reset Location', - value: localize('resetViewLocation', "Reset Location") - }, - menu: [{ - id: MenuId.ViewTitleContext, - when: ContextKeyExpr.or( - ContextKeyExpr.and( - ContextKeyExpr.equals('view', viewDescriptor.id), - ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false) - ) - ), - group: '1_hide', - order: 2 - }], - }); - } - run(accessor: ServicesAccessor): void { - const viewDescriptorService = accessor.get(IViewDescriptorService); - const defaultContainer = viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!; - const containerModel = viewDescriptorService.getViewContainerModel(defaultContainer)!; - - // The default container is hidden so we should try to reset its location first - if (defaultContainer.hideIfEmpty && containerModel.visibleViewDescriptors.length === 0) { - const defaultLocation = viewDescriptorService.getDefaultViewContainerLocation(defaultContainer)!; - viewDescriptorService.moveViewContainerToLocation(defaultContainer, defaultLocation); - } - - viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!); - accessor.get(IViewsService).openView(viewDescriptor.id, true); - } - })); - + disposables.add(this.registerOpenViewAction(viewDescriptor)); + disposables.add(this.registerFocusViewAction(viewDescriptor, composite?.name ?? CATEGORIES.View)); + disposables.add(this.registerResetViewLocationAction(viewDescriptor)); this.viewDisposable.set(viewDescriptor, disposables); } } @@ -543,6 +361,202 @@ export class ViewsService extends Disposable implements IViewsService { return this.viewDescriptorService.getViewContainerLocation(viewContainer) === ViewContainerLocation.Sidebar ? this.viewletService.getProgressIndicator(viewContainer.id) : this.panelService.getProgressIndicator(viewContainer.id); } + private registerOpenViewContainerAction(viewContainer: ViewContainer): IDisposable { + const disposables = new DisposableStore(); + if (viewContainer.openCommandActionDescriptor) { + let { id, title, mnemonicTitle, keybindings, order } = viewContainer.openCommandActionDescriptor ?? { id: viewContainer.id }; + title = title ?? viewContainer.title; + const that = this; + disposables.add(registerAction2(class OpenViewContainerAction extends Action2 { + constructor() { + super({ + id, + get title(): ICommandActionTitle { + const viewContainerLocation = that.viewDescriptorService.getViewContainerLocation(viewContainer); + if (viewContainerLocation === ViewContainerLocation.Sidebar) { + return { value: localize('show view', "Show {0}", title), original: `Show ${title}` }; + } else { + return { value: localize('toggle view', "Toggle {0}", title), original: `Toggle ${title}` }; + } + }, + category: CATEGORIES.View.value, + precondition: ContextKeyExpr.has(getEnabledViewContainerContextKey(viewContainer.id)), + keybinding: keybindings ? { ...keybindings, weight: KeybindingWeight.WorkbenchContrib } : undefined, + f1: true + }); + } + public async run(serviceAccessor: ServicesAccessor): Promise { + const editorGroupService = serviceAccessor.get(IEditorGroupsService); + const viewDescriptorService = serviceAccessor.get(IViewDescriptorService); + const layoutService = serviceAccessor.get(IWorkbenchLayoutService); + const viewsService = serviceAccessor.get(IViewsService); + const viewContainerLocation = viewDescriptorService.getViewContainerLocation(viewContainer); + switch (viewContainerLocation) { + case ViewContainerLocation.Sidebar: + if (!viewsService.isViewContainerVisible(viewContainer.id) || !layoutService.hasFocus(Parts.SIDEBAR_PART)) { + await viewsService.openViewContainer(viewContainer.id, true); + } else { + editorGroupService.activeGroup.focus(); + } + break; + case ViewContainerLocation.Panel: + if (!viewsService.isViewContainerVisible(viewContainer.id) || !layoutService.hasFocus(Parts.PANEL_PART)) { + await viewsService.openViewContainer(viewContainer.id, true); + } else { + viewsService.closeViewContainer(viewContainer.id); + } + break; + } + } + })); + + if (mnemonicTitle) { + const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer); + disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + command: { + id, + title: mnemonicTitle, + }, + group: defaultLocation === ViewContainerLocation.Sidebar ? '3_views' : '4_panels', + when: ContextKeyExpr.has(getEnabledViewContainerContextKey(viewContainer.id)), + order: order ?? Number.MAX_VALUE + })); + } + } + + return disposables; + } + + private registerOpenViewAction(viewDescriptor: IViewDescriptor): IDisposable { + const disposables = new DisposableStore(); + if (viewDescriptor.openCommandActionDescriptor) { + const title = viewDescriptor.openCommandActionDescriptor.title ?? viewDescriptor.name; + const commandId = viewDescriptor.openCommandActionDescriptor.id; + const that = this; + disposables.add(registerAction2(class OpenViewAction extends Action2 { + constructor() { + super({ + id: commandId, + get title(): ICommandActionTitle { + const viewContainerLocation = that.viewDescriptorService.getViewLocationById(viewDescriptor.id); + if (viewContainerLocation === ViewContainerLocation.Sidebar) { + return { value: localize('show view', "Show {0}", title), original: `Show ${title}` }; + } else { + return { value: localize('toggle view', "Toggle {0}", title), original: `Toggle ${title}` }; + } + }, + category: CATEGORIES.View.value, + precondition: ContextKeyDefinedExpr.create(`${viewDescriptor.id}.active`), + keybinding: viewDescriptor.openCommandActionDescriptor!.keybindings ? { ...viewDescriptor.openCommandActionDescriptor!.keybindings, weight: KeybindingWeight.WorkbenchContrib } : undefined, + f1: true + }); + } + public async run(serviceAccessor: ServicesAccessor): Promise { + const editorGroupService = serviceAccessor.get(IEditorGroupsService); + const viewDescriptorService = serviceAccessor.get(IViewDescriptorService); + const layoutService = serviceAccessor.get(IWorkbenchLayoutService); + const viewsService = serviceAccessor.get(IViewsService); + const contextKeyService = serviceAccessor.get(IContextKeyService); + + const focusedViewId = FocusedViewContext.getValue(contextKeyService); + if (focusedViewId === viewDescriptor.id) { + if (viewDescriptorService.getViewLocationById(viewDescriptor.id) === ViewContainerLocation.Sidebar) { + editorGroupService.activeGroup.focus(); + } else { + layoutService.setPanelHidden(true); + } + } else { + viewsService.openView(viewDescriptor.id, true); + } + } + })); + + if (viewDescriptor.openCommandActionDescriptor.mnemonicTitle) { + const defaultViewContainer = this.viewDescriptorService.getDefaultContainerById(viewDescriptor.id); + if (defaultViewContainer) { + const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(defaultViewContainer); + disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + command: { + id: commandId, + title: viewDescriptor.openCommandActionDescriptor.mnemonicTitle, + }, + group: defaultLocation === ViewContainerLocation.Sidebar ? '3_views' : '4_panels', + when: ContextKeyDefinedExpr.create(`${viewDescriptor.id}.active`), + order: viewDescriptor.openCommandActionDescriptor.order ?? Number.MAX_VALUE + })); + } + } + } + return disposables; + } + + private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable { + return registerAction2(class FocusViewAction extends Action2 { + constructor() { + super({ + id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, + title: { original: `Focus on ${viewDescriptor.name} View`, value: localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name) }, + category, + menu: [{ + id: MenuId.CommandPalette, + when: viewDescriptor.when, + }], + keybinding: { + when: ContextKeyExpr.has(`${viewDescriptor.id}.active`), + weight: KeybindingWeight.WorkbenchContrib, + primary: viewDescriptor.focusCommand?.keybindings?.primary, + secondary: viewDescriptor.focusCommand?.keybindings?.secondary, + linux: viewDescriptor.focusCommand?.keybindings?.linux, + mac: viewDescriptor.focusCommand?.keybindings?.mac, + win: viewDescriptor.focusCommand?.keybindings?.win + } + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IViewsService).openView(viewDescriptor.id, true); + } + }); + } + + private registerResetViewLocationAction(viewDescriptor: IViewDescriptor): IDisposable { + return registerAction2(class ResetViewLocationAction extends Action2 { + constructor() { + super({ + id: `${viewDescriptor.id}.resetViewLocation`, + title: { + original: 'Reset Location', + value: localize('resetViewLocation', "Reset Location") + }, + menu: [{ + id: MenuId.ViewTitleContext, + when: ContextKeyExpr.or( + ContextKeyExpr.and( + ContextKeyExpr.equals('view', viewDescriptor.id), + ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false) + ) + ), + group: '1_hide', + order: 2 + }], + }); + } + run(accessor: ServicesAccessor): void { + const viewDescriptorService = accessor.get(IViewDescriptorService); + const defaultContainer = viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!; + const containerModel = viewDescriptorService.getViewContainerModel(defaultContainer)!; + + // The default container is hidden so we should try to reset its location first + if (defaultContainer.hideIfEmpty && containerModel.visibleViewDescriptors.length === 0) { + const defaultLocation = viewDescriptorService.getDefaultViewContainerLocation(defaultContainer)!; + viewDescriptorService.moveViewContainerToLocation(defaultContainer, defaultLocation); + } + + viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!); + accessor.get(IViewsService).openView(viewDescriptor.id, true); + } + }); + } + private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { switch (viewContainerLocation) { case ViewContainerLocation.Panel: diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 1a492c81fde..90a0191738a 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -48,7 +48,7 @@ export function ViewContainerLocationToString(viewContainerLocation: ViewContain } } -type CommandActionDescriptor = { +type OpenCommandActionDescriptor = { readonly id: string; readonly title?: string; readonly mnemonicTitle?: string; @@ -89,9 +89,12 @@ export interface IViewContainerDescriptor { readonly ctorDescriptor: SyncDescriptor; /** - * Command action descriptor to be registered + * Descriptor for open view container command + * If not provided, view container info (id, title) is used. + * + * Note: To prevent registering open command, use `donotRegisterOpenCommand` flag while registering the view container */ - readonly commandActionDescriptor?: CommandActionDescriptor | false; + readonly openCommandActionDescriptor?: OpenCommandActionDescriptor; /** * Storage id to use to store the view container state. @@ -143,7 +146,7 @@ export interface IViewContainersRegistry { * * @returns the registered ViewContainer. */ - registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation, isDefault?: boolean): ViewContainer; + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation, options?: { isDefault?: boolean, donotRegisterOpenCommand?: boolean }): ViewContainer; /** * Deregisters the given view container @@ -180,6 +183,11 @@ interface ViewOrderDelegate { export interface ViewContainer extends IViewContainerDescriptor { } +interface RelaxedViewContainer extends ViewContainer { + + openCommandActionDescriptor?: OpenCommandActionDescriptor; +} + class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { private readonly _onDidRegister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>()); @@ -195,16 +203,17 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe return flatten([...this.viewContainers.values()]); } - registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation, isDefault?: boolean): ViewContainer { + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation, options?: { isDefault?: boolean, donotRegisterOpenCommand?: boolean }): ViewContainer { const existing = this.get(viewContainerDescriptor.id); if (existing) { return existing; } - const viewContainer: ViewContainer = viewContainerDescriptor; + const viewContainer: RelaxedViewContainer = viewContainerDescriptor; + viewContainer.openCommandActionDescriptor = options?.donotRegisterOpenCommand ? undefined : (viewContainer.openCommandActionDescriptor ?? { id: viewContainer.id }); const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []); viewContainers.push(viewContainer); - if (isDefault) { + if (options?.isDefault) { this.defaultViewContainers.push(viewContainer); } this._onDidRegister.fire({ viewContainer, viewContainerLocation }); @@ -283,7 +292,7 @@ export interface IViewDescriptor { readonly remoteAuthority?: string | string[]; - readonly commandActionDescriptor?: CommandActionDescriptor + readonly openCommandActionDescriptor?: OpenCommandActionDescriptor } export interface IViewDescriptorRef { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index f6dd6daccbf..28086409884 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -334,8 +334,7 @@ function registerDebugPanel(): void { ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: DEBUG_PANEL_ID, hideIfEmpty: true, - commandActionDescriptor: false, - }, ViewContainerLocation.Panel); + }, ViewContainerLocation.Panel, { donotRegisterOpenCommand: true }); Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ id: REPL_VIEW_ID, @@ -345,7 +344,7 @@ function registerDebugPanel(): void { canMoveView: true, when: CONTEXT_DEBUGGERS_AVAILABLE, ctorDescriptor: new SyncDescriptor(Repl), - commandActionDescriptor: { + openCommandActionDescriptor: { id: 'workbench.debug.action.toggleRepl', mnemonicTitle: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y }, @@ -359,7 +358,7 @@ function registerDebugView(): void { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, title: nls.localize('run and debug', "Run and Debug"), - commandActionDescriptor: { + openCommandActionDescriptor: { id: VIEWLET_ID, mnemonicTitle: nls.localize({ key: 'miViewRun', comment: ['&& denotes a mnemonic'] }, "&&Run"), keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D }, @@ -457,6 +456,11 @@ function registerConfiguration(): void { description: nls.localize('debug.console.historySuggestions', "Controls if the debug console should suggest previously typed input."), default: true }, + 'debug.console.collapseIdenticalLines': { + type: 'boolean', + description: nls.localize('debug.console.collapseIdenticalLines', "Controls if the debug console should collapse identical lines and show a number of occurrences with a badge."), + default: true + }, 'launch': { type: 'object', description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."), diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 51e31986081..b75495ba50d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -88,7 +88,7 @@ export class DebugSession implements IDebugSession { ) { this._options = options || {}; if (this.hasSeparateRepl()) { - this.repl = new ReplModel(); + this.repl = new ReplModel(this.configurationService); } else { this.repl = (this.parentSession as DebugSession).repl; } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 1afbbaf1232..8e0a4c358c8 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -578,7 +578,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { accessibilityProvider: new ReplAccessibilityProvider(), identityProvider, mouseSupport: false, - keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e }, + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e.toString(true) }, horizontalScrolling: !wordWrap, setRowLineHeight: false, supportDynamicHeights: wordWrap, diff --git a/src/vs/workbench/contrib/debug/browser/replFilter.ts b/src/vs/workbench/contrib/debug/browser/replFilter.ts index 3cb5e45d844..e18a422896c 100644 --- a/src/vs/workbench/contrib/debug/browser/replFilter.ts +++ b/src/vs/workbench/contrib/debug/browser/replFilter.ts @@ -61,7 +61,7 @@ export class ReplFilter implements ITreeFilter { let includeQueryPresent = false; let includeQueryMatched = false; - const text = element.toString(); + const text = element.toString(true); for (let { type, query } of this._parsedQueries) { if (type === 'exclude' && ReplFilter.matchQuery(query, text)) { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index a0bac814b04..b468a45a486 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -107,7 +107,7 @@ export interface ITreeElement { } export interface IReplElement extends ITreeElement { - toString(): string; + toString(includeSource?: boolean): string; readonly sourceData?: IReplElementSource; } @@ -504,6 +504,7 @@ export interface IDebugConfiguration { lineHeight: number; wordWrap: boolean; closeOnEnd: boolean; + collapseIdenticalLines: boolean; historySuggestions: boolean; }; focusWindowOnBreak: boolean; diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index be45bc3742d..a4a20e491af 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -5,13 +5,14 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; -import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel'; import { isString, isUndefinedOrNull, isObject } from 'vs/base/common/types'; import { basenameOrAuthority } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { Emitter, Event } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const MAX_REPL_LENGTH = 10000; let topReplElementCounter = 0; @@ -29,12 +30,12 @@ export class SimpleReplElement implements IReplElement { public sourceData?: IReplElementSource, ) { } - toString(): string { + toString(includeSource = false): string { let valueRespectCount = this.value; for (let i = 1; i < this.count; i++) { valueRespectCount += (valueRespectCount.endsWith('\n') ? '' : '\n') + this.value; } - const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : ''; + const sourceStr = (this.sourceData && includeSource) ? ` ${this.sourceData.source.name}` : ''; return valueRespectCount + sourceStr; } @@ -164,8 +165,8 @@ export class ReplGroup implements IReplElement { return this.id; } - toString(): string { - const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : ''; + toString(includeSource = false): string { + const sourceStr = (includeSource && this.sourceData) ? ` ${this.sourceData.source.name}` : ''; return this.name + sourceStr; } @@ -201,6 +202,8 @@ export class ReplModel { private readonly _onDidChangeElements = new Emitter(); readonly onDidChangeElements = this._onDidChangeElements.event; + constructor(private readonly configurationService: IConfigurationService) { } + getReplElements(): IReplElement[] { return this.replElements; } @@ -224,7 +227,8 @@ export class ReplModel { if (typeof data === 'string') { const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined; if (previousElement instanceof SimpleReplElement && previousElement.severity === sev) { - if (previousElement.value === data) { + const config = this.configurationService.getValue('debug'); + if (previousElement.value === data && config.console.collapseIdenticalLines) { previousElement.count++; // No need to fire an event, just the count updates and badge will adjust automatically return; diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index dd694374635..9a1efdb443d 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -19,6 +19,7 @@ import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug import { generateUuid } from 'vs/base/common/uuid'; import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; const mockWorkspaceContextService = { getWorkspace: () => { @@ -37,7 +38,7 @@ export function createMockSession(model: DebugModel, name = 'mockSession', optio } }; } - } as IDebugService, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService); + } as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 22d3876bfa3..aa0424413c7 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -14,10 +14,12 @@ import { timeout } from 'vs/base/common/async'; import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter'; import { TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; suite('Debug - REPL', () => { let model: DebugModel; let rawSession: MockRawSession; + const configurationService = new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }); setup(() => { model = createMockDebugModel(); @@ -26,7 +28,7 @@ suite('Debug - REPL', () => { test('repl output', () => { const session = createMockSession(model); - const repl = new ReplModel(); + const repl = new ReplModel(configurationService); repl.appendToRepl(session, 'first line\n', severity.Error); repl.appendToRepl(session, 'second line ', severity.Error); repl.appendToRepl(session, 'third line ', severity.Error); @@ -84,7 +86,7 @@ suite('Debug - REPL', () => { test('repl output count', () => { const session = createMockSession(model); - const repl = new ReplModel(); + const repl = new ReplModel(configurationService); repl.appendToRepl(session, 'first line\n', severity.Info); repl.appendToRepl(session, 'first line\n', severity.Info); repl.appendToRepl(session, 'first line\n', severity.Info); @@ -155,7 +157,7 @@ suite('Debug - REPL', () => { session['raw'] = rawSession; const thread = new Thread(session, 'mockthread', 1); const stackFrame = new StackFrame(thread, 1, undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); - const replModel = new ReplModel(); + const replModel = new ReplModel(configurationService); replModel.addReplExpression(session, stackFrame, 'myVariable').then(); replModel.addReplExpression(session, stackFrame, 'myVariable').then(); replModel.addReplExpression(session, stackFrame, 'myVariable').then(); @@ -193,7 +195,7 @@ suite('Debug - REPL', () => { test('repl groups', async () => { const session = createMockSession(model); - const repl = new ReplModel(); + const repl = new ReplModel(configurationService); repl.appendToRepl(session, 'first global line', severity.Info); repl.startGroup('group_1', true); @@ -231,7 +233,7 @@ suite('Debug - REPL', () => { test('repl filter', async () => { const session = createMockSession(model); - const repl = new ReplModel(); + const repl = new ReplModel(configurationService); const replFilter = new ReplFilter(); const getFilteredElements = () => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 165caad9b9d..bd2454e646d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -102,7 +102,7 @@ Registry.as(ViewContainerExtensions.ViewContainersRegis { id: VIEWLET_ID, title: localize('extensions', "Extensions"), - commandActionDescriptor: { + openCommandActionDescriptor: { id: VIEWLET_ID, mnemonicTitle: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X }, diff --git a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts index 069ad5e484a..ad371f4fb0b 100644 --- a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts +++ b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts @@ -45,6 +45,12 @@ export interface IExternalUriOpenerService { * Registers a provider for external resources openers. */ registerExternalOpenerProvider(provider: IExternalOpenerProvider): IDisposable; + + /** + * Get the configured IExternalUriOpener for the the uri. + * If there is no opener configured, then returns the first opener that can handle the uri. + */ + getOpener(uri: URI, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise; } export class ExternalUriOpenerService extends Disposable implements IExternalUriOpenerService, IExternalOpener { @@ -70,26 +76,23 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri return { dispose: remove }; } - async openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise { - - const targetUri = typeof href === 'string' ? URI.parse(href) : href; - + private async getOpeners(targetUri: URI, allowOptional: boolean, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise { const allOpeners = await this.getAllOpenersForUri(targetUri); if (allOpeners.size === 0) { - return false; + return []; } // First see if we have a preferredOpener if (ctx.preferredOpenerId) { if (ctx.preferredOpenerId === defaultExternalUriOpenerId) { - return false; + return []; } const preferredOpener = allOpeners.get(ctx.preferredOpenerId); if (preferredOpener) { // Skip the `canOpen` check here since the opener was specifically requested. - return preferredOpener.openExternalUri(targetUri, ctx, token); + return [preferredOpener]; } } @@ -97,7 +100,7 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri const configuredOpener = this.getConfiguredOpenerForUri(allOpeners, targetUri); if (configuredOpener) { // Skip the `canOpen` check here since the opener was specifically requested. - return configuredOpener === defaultExternalUriOpenerId ? false : configuredOpener.openExternalUri(targetUri, ctx, token); + return configuredOpener === defaultExternalUriOpenerId ? [] : [configuredOpener]; } // Then check to see if there is a valid opener @@ -121,22 +124,44 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri })); if (validOpeners.length === 0) { - return false; + return []; } // See if we have a preferred opener first const preferred = firstOrDefault(validOpeners.filter(x => x.priority === modes.ExternalUriOpenerPriority.Preferred)); if (preferred) { - return preferred.opener.openExternalUri(targetUri, ctx, token); + return [preferred.opener]; } // See if we only have optional openers, use the default opener - if (validOpeners.every(x => x.priority === modes.ExternalUriOpenerPriority.Option)) { + if (!allowOptional && validOpeners.every(x => x.priority === modes.ExternalUriOpenerPriority.Option)) { + return []; + } + + return validOpeners.map(value => value.opener); + } + + async openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise { + + const targetUri = typeof href === 'string' ? URI.parse(href) : href; + + const allOpeners = await this.getOpeners(targetUri, false, ctx, token); + if (allOpeners.length === 0) { return false; + } else if (allOpeners.length === 1) { + return allOpeners[0].openExternalUri(targetUri, ctx, token); } // Otherwise prompt - return this.showOpenerPrompt(validOpeners.map(x => x.opener), targetUri, ctx, token); + return this.showOpenerPrompt(allOpeners, targetUri, ctx, token); + } + + async getOpener(targetUri: URI, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise { + const allOpeners = await this.getOpeners(targetUri, true, ctx, token); + if (allOpeners.length >= 1) { + return allOpeners[0]; + } + return undefined; } private async getAllOpenersForUri(targetUri: URI): Promise> { diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index d6dcc9292e9..8febbfd97c1 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -118,7 +118,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor when: OpenEditorsVisibleContext, canToggleVisibility: true, canMoveView: true, - collapsed: true, + collapsed: false, hideByDefault: true, focusCommand: { id: 'workbench.files.action.focusOpenEditorsView', @@ -149,7 +149,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor ctorDescriptor: new SyncDescriptor(ExplorerView), order: 1, canToggleVisibility: false, - commandActionDescriptor: { + openCommandActionDescriptor: { id: VIEW_CONTAINER.id, title: localize('explore', "Explorer"), mnemonicTitle: localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), @@ -283,8 +283,7 @@ export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewC icon: explorerViewIcon, alwaysUseContainerInfo: true, order: 0, - commandActionDescriptor: false, -}, ViewContainerLocation.Sidebar, true); +}, ViewContainerLocation.Sidebar, { donotRegisterOpenCommand: true, isDefault: true }); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index c9b2dcd2a7f..e19fa398b85 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -227,6 +227,8 @@ appendToCommandPalette(NEW_UNTITLED_FILE_COMMAND_ID, { value: NEW_UNTITLED_FILE_ // Menu registration - open editors +const isFileOrUntitledResourceContextKey = ContextKeyExpr.or(ResourceContextKey.IsFileSystemResource, ResourceContextKey.Scheme.isEqualTo(Schemas.untitled)); + const openToSideCommand = { id: OPEN_TO_SIDE_COMMAND_ID, title: nls.localize('openToSide', "Open to the Side") @@ -235,7 +237,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', order: 10, command: openToSideCommand, - when: ContextKeyExpr.or(ResourceContextKey.IsFileSystemResource, ResourceContextKey.Scheme.isEqualTo(Schemas.untitled)) + when: isFileOrUntitledResourceContextKey }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -326,7 +328,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '3_compare', order: 20, command: compareResourceCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, ResourceSelectedForCompareContext, WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, ResourceSelectedForCompareContext, isFileOrUntitledResourceContextKey, WorkbenchListDoubleSelection.toNegated()) }); const selectForCompareCommand = { @@ -337,7 +339,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '3_compare', order: 30, command: selectForCompareCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, isFileOrUntitledResourceContextKey, WorkbenchListDoubleSelection.toNegated()) }); const compareSelectedCommand = { @@ -348,7 +350,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '3_compare', order: 30, command: compareSelectedCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, WorkbenchListDoubleSelection) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, WorkbenchListDoubleSelection, isFileOrUntitledResourceContextKey) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index bd9534dffb1..e0d049f9a56 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -115,8 +115,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC order: 0, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: Constants.MARKERS_VIEW_STORAGE_ID, - commandActionDescriptor: false, -}, ViewContainerLocation.Panel); +}, ViewContainerLocation.Panel, { donotRegisterOpenCommand: true }); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: Constants.MARKERS_VIEW_ID, @@ -125,7 +124,7 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(MarkersView), - commandActionDescriptor: { + openCommandActionDescriptor: { id: 'workbench.actions.view.problems', mnemonicTitle: localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M }, diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts index 8a0e7216d60..6a52ef64a97 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts @@ -10,9 +10,9 @@ import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/termi import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ICommonNotebookEditor, IErrorOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -class ErrorTransform implements IOutputTransformContribution { +export class ErrorTransform implements IOutputTransformContribution { constructor( - public editor: ICommonNotebookEditor, + _editor: ICommonNotebookEditor, @IThemeService private readonly themeService: IThemeService ) { } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 420f309b407..88deceb6225 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -20,6 +20,7 @@ import { dirname } from 'vs/base/common/resources'; import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ErrorTransform } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; class RichRenderer implements IOutputTransformContribution { private _richMimeTypeRenderers = new Map IRenderOutput>(); @@ -42,6 +43,7 @@ class RichRenderer implements IOutputTransformContribution { this._richMimeTypeRenderers.set('image/jpeg', this.renderJPEG.bind(this)); this._richMimeTypeRenderers.set('text/plain', this.renderPlainText.bind(this)); this._richMimeTypeRenderers.set('text/x-javascript', this.renderCode.bind(this)); + this._richMimeTypeRenderers.set('application/x.notebook.error-traceback', this._renderErrorTraceback.bind(this)); } render(output: IDisplayOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput { @@ -215,6 +217,14 @@ class RichRenderer implements IOutputTransformContribution { return { type: RenderOutputType.None, hasDynamicHeight: false }; } + private _renderErrorTraceback(outputViewModel: IDisplayOutputViewModel, _notebookUri: URI, container: HTMLElement): IRenderOutput { + const output = outputViewModel.model.data['application/x.notebook.error-traceback'] as any; + const transform = new ErrorTransform(this.notebookEditor, this.themeService); + transform.render(output, container); + transform.dispose(); + return { type: RenderOutputType.None, hasDynamicHeight: false }; + } + dispose(): void { } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 7ad9daf8e00..15832370af0 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -339,7 +339,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel //TODO@jrieken,@rebornix no event, no undo stop (?) this._assertIndex(edit.index); const cell = this._cells[edit.index]; - this._spliceNotebookCellOutputs2(cell.handle, edit.outputs, computeUndoRedo); + if (edit.append) { + this._spliceNotebookCellOutputs(cell.handle, [[cell.outputs.length, 0, edit.outputs]], computeUndoRedo); + } else { + this._spliceNotebookCellOutputs2(cell.handle, edit.outputs, computeUndoRedo); + } break; case CellEditType.OutputsSplice: { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index cf462b90514..b6ea52aaddf 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -280,9 +280,9 @@ export type NotebookCellTextModelSplice = [ ]; export type NotebookCellOutputsSplice = [ - number /* start */, - number /* delete count */, - IProcessedOutput[] + start: number /* start */, + deleteCount: number /* delete count */, + newOutputs: IProcessedOutput[] ]; export interface IMainCellDto { @@ -411,6 +411,7 @@ export interface ICellOutputEdit { editType: CellEditType.Output; index: number; outputs: IProcessedOutput[]; + append?: boolean } export interface ICellMetadataEdit { @@ -524,35 +525,30 @@ export namespace CellUri { } } -export function mimeTypeIsAlwaysSecure(mimeType: string) { - if ([ - 'application/json', - 'text/markdown', - 'image/png', - 'text/plain' - ].indexOf(mimeType) > -1) { - return true; - } +type MimeTypeInfo = { + alwaysSecure?: boolean; + supportedByCore?: boolean; +}; - return false; +const _mimeTypeInfo = new Map([ + ['application/json', { alwaysSecure: true, supportedByCore: true }], + ['text/markdown', { alwaysSecure: true, supportedByCore: true }], + ['image/png', { alwaysSecure: true, supportedByCore: true }], + ['text/plain', { alwaysSecure: true, supportedByCore: true }], + ['application/javascript', { supportedByCore: true }], + ['text/html', { supportedByCore: true }], + ['image/svg+xml', { supportedByCore: true }], + ['image/jpeg', { supportedByCore: true }], + ['text/x-javascript', { supportedByCore: true }], + ['application/x.notebook.error-traceback', { alwaysSecure: true, supportedByCore: true }], +]); + +export function mimeTypeIsAlwaysSecure(mimeType: string): boolean { + return _mimeTypeInfo.get(mimeType)?.alwaysSecure ?? false; } export function mimeTypeSupportedByCore(mimeType: string) { - if ([ - 'application/json', - 'application/javascript', - 'text/html', - 'image/svg+xml', - 'text/markdown', - 'image/png', - 'image/jpeg', - 'text/plain', - 'text/x-javascript' - ].indexOf(mimeType) > -1) { - return true; - } - - return false; + return _mimeTypeInfo.get(mimeType)?.supportedByCore ?? false; } // if (isWindows) { diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index 93572f226ee..c1c13513e9b 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -9,6 +9,7 @@ import { withTestNotebook, TestCell, setupInstantiationService } from 'vs/workbe import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { assertType } from 'vs/base/common/types'; suite('NotebookTextModel', () => { const instantiationService = setupInstantiationService(); @@ -202,9 +203,46 @@ suite('NotebookTextModel', () => { }] }], true, undefined, () => undefined, undefined); - assert.equal(textModel.cells.length, 1); - assert.equal(textModel.cells[0].outputs.length, 1); - assert.equal(textModel.cells[0].outputs[0].outputKind, CellOutputKind.Rich); + assert.strictEqual(textModel.cells.length, 1); + assert.strictEqual(textModel.cells[0].outputs.length, 1); + assert.strictEqual(textModel.cells[0].outputs[0].outputKind, CellOutputKind.Rich); + + // append + textModel.applyEdits(textModel.versionId, [{ + index: 0, + editType: CellEditType.Output, + append: true, + outputs: [{ + outputKind: CellOutputKind.Rich, + outputId: 'someId2', + data: { 'text/markdown': '_Hello2_' } + }] + }], true, undefined, () => undefined, undefined); + + assert.strictEqual(textModel.cells.length, 1); + assert.strictEqual(textModel.cells[0].outputs.length, 2); + let [first, second] = textModel.cells[0].outputs; + assertType(first.outputKind === CellOutputKind.Rich); + assertType(second.outputKind === CellOutputKind.Rich); + assert.strictEqual(first.outputId, 'someId'); + assert.strictEqual(second.outputId, 'someId2'); + + // replace all + textModel.applyEdits(textModel.versionId, [{ + index: 0, + editType: CellEditType.Output, + outputs: [{ + outputKind: CellOutputKind.Rich, + outputId: 'someId3', + data: { 'text/plain': 'Last, replaced output' } + }] + }], true, undefined, () => undefined, undefined); + + assert.strictEqual(textModel.cells.length, 1); + assert.strictEqual(textModel.cells[0].outputs.length, 1); + [first] = textModel.cells[0].outputs; + assertType(first.outputKind === CellOutputKind.Rich); + assert.strictEqual(first.outputId, 'someId3'); } ); }); diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 5a42221b2ec..0795ae19c99 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -60,8 +60,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: OUTPUT_VIEW_ID, hideIfEmpty: true, - commandActionDescriptor: false, -}, ViewContainerLocation.Panel); +}, ViewContainerLocation.Panel, { donotRegisterOpenCommand: true }); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: OUTPUT_VIEW_ID, @@ -70,7 +69,7 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews canMoveView: true, canToggleVisibility: false, ctorDescriptor: new SyncDescriptor(OutputViewPane), - commandActionDescriptor: { + openCommandActionDescriptor: { id: 'workbench.action.output.toggleOutput', mnemonicTitle: nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), keybindings: { diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index ceed57b9a18..d9e0ea9eb73 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -7,7 +7,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PortsAttributes, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { PORT_AUTO_FORWARD_SETTING, forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { PORT_AUTO_FORWARD_SETTING, forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,6 +29,7 @@ import { ITASExperimentService } from 'vs/workbench/services/experiment/common/e import { optional } from 'vs/platform/instantiation/common/instantiation'; import { portsViewIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; import { Event } from 'vs/base/common/event'; +import { IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; export const VIEWLET_ID = 'workbench.view.remote'; @@ -196,6 +197,7 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon @ITerminalService readonly terminalService: ITerminalService, @INotificationService readonly notificationService: INotificationService, @IOpenerService readonly openerService: IOpenerService, + @IExternalUriOpenerService readonly externalOpenerService: IExternalUriOpenerService, @IViewsService readonly viewsService: IViewsService, @IRemoteExplorerService readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, @@ -212,11 +214,12 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon remoteAgentService.getEnvironment().then(environment => { if (environment?.os === OperatingSystem.Windows) { - this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, + this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService, remoteExplorerService, configurationService, debugService, tunnelService, remoteAgentService, false)); } else if (environment?.os === OperatingSystem.Linux) { - this._register(new ProcAutomaticPortForwarding(configurationService, remoteExplorerService, notificationService, openerService, tunnelService)); - this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, + this._register(new ProcAutomaticPortForwarding(configurationService, remoteExplorerService, notificationService, + openerService, externalOpenerService, tunnelService)); + this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService, remoteExplorerService, configurationService, debugService, tunnelService, remoteAgentService, true)); } }); @@ -232,6 +235,7 @@ class OnAutoForwardedAction extends Disposable { constructor(private readonly notificationService: INotificationService, private readonly remoteExplorerService: IRemoteExplorerService, private readonly openerService: IOpenerService, + private readonly externalOpenerService: IExternalUriOpenerService, private readonly tunnelService: ITunnelService, private readonly portsAttributes: PortsAttributes) { super(); @@ -248,6 +252,11 @@ class OnAutoForwardedAction extends Disposable { await OpenPortInBrowserAction.run(this.remoteExplorerService.tunnelModel, this.openerService, address); break; } + case OnPortForward.OpenPreview: { + const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + await OpenPortInPreviewAction.run(this.remoteExplorerService.tunnelModel, this.openerService, this.externalOpenerService, address); + break; + } case OnPortForward.Silent: break; default: if (Date.now() - this.lastNotifyTime.getTime() > OnAutoForwardedAction.NOTIFY_COOL_DOWN) { @@ -306,7 +315,7 @@ class OnAutoForwardedAction extends Disposable { this.lastNotification.close(); } let message = this.basicMessage(tunnel); - const choices = [this.openChoice(tunnel)]; + const choices = [this.openBrowserChoice(tunnel), this.openPreviewChoice(tunnel)]; if ((tunnel.tunnelLocalPort !== tunnel.tunnelRemotePort) && this.tunnelService.canElevate && isPortPrivileged(tunnel.tunnelRemotePort)) { // Privileged ports are not on Windows, so it's safe to use "superuser" @@ -325,7 +334,7 @@ class OnAutoForwardedAction extends Disposable { }); } - private openChoice(tunnel: RemoteTunnel): IPromptChoice { + private openBrowserChoice(tunnel: RemoteTunnel): IPromptChoice { const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); return { label: OpenPortInBrowserAction.LABEL, @@ -333,6 +342,14 @@ class OnAutoForwardedAction extends Disposable { }; } + private openPreviewChoice(tunnel: RemoteTunnel): IPromptChoice { + const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + return { + label: OpenPortInPreviewAction.LABEL, + run: () => OpenPortInPreviewAction.run(this.remoteExplorerService.tunnelModel, this.openerService, this.externalOpenerService, address) + }; + } + private elevateChoice(tunnel: RemoteTunnel): IPromptChoice { return { // Privileged ports are not on Windows, so it's ok to stick to just "sudo". @@ -347,7 +364,10 @@ class OnAutoForwardedAction extends Disposable { this.lastNotification.close(); } this.lastShownPort = newTunnel.tunnelRemotePort; - this.lastNotification = this.notificationService.prompt(Severity.Info, this.basicMessage(newTunnel) + this.linkMessage(), [this.openChoice(newTunnel)], { neverShowAgain: { id: 'remote.tunnelsView.autoForwardNeverShow', isSecondary: true } }); + this.lastNotification = this.notificationService.prompt(Severity.Info, + this.basicMessage(newTunnel) + this.linkMessage(), + [this.openBrowserChoice(newTunnel), this.openPreviewChoice(tunnel)], + { neverShowAgain: { id: 'remote.tunnelsView.autoForwardNeverShow', isSecondary: true } }); this.lastNotification.onDidClose(() => { this.lastNotification = undefined; this.lastShownPort = undefined; @@ -367,6 +387,7 @@ class OutputAutomaticPortForwarding extends Disposable { private readonly terminalService: ITerminalService, readonly notificationService: INotificationService, readonly openerService: IOpenerService, + readonly externalOpenerService: IExternalUriOpenerService, private readonly remoteExplorerService: IRemoteExplorerService, private readonly configurationService: IConfigurationService, private readonly debugService: IDebugService, @@ -376,7 +397,7 @@ class OutputAutomaticPortForwarding extends Disposable { ) { super(); this.portsAttributes = new PortsAttributes(configurationService); - this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, tunnelService, this.portsAttributes); + this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, this.portsAttributes); this._register(configurationService.onDidChangeConfiguration((e) => { if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) { this.tryStartStopUrlFinder(); @@ -443,11 +464,12 @@ class ProcAutomaticPortForwarding extends Disposable { readonly remoteExplorerService: IRemoteExplorerService, readonly notificationService: INotificationService, readonly openerService: IOpenerService, + readonly externalOpenerService: IExternalUriOpenerService, readonly tunnelService: ITunnelService ) { super(); this.portsAttributes = new PortsAttributes(configurationService); - this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, tunnelService, this.portsAttributes); + this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, this.portsAttributes); this._register(configurationService.onDidChangeConfiguration(async (e) => { if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) { await this.startStopCandidateListener(); diff --git a/src/vs/workbench/contrib/remote/browser/remoteIcons.ts b/src/vs/workbench/contrib/remote/browser/remoteIcons.ts index 526b7cc0629..e1ebf4235eb 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIcons.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIcons.ts @@ -23,3 +23,4 @@ export const publicPortIcon = registerIcon('public-ports-view-icon', Codicon.eye export const forwardPortIcon = registerIcon('ports-forward-icon', Codicon.plus, nls.localize('forwardPortIcon', 'Icon for the forward action.')); export const stopForwardIcon = registerIcon('ports-stop-forward-icon', Codicon.x, nls.localize('stopForwardIcon', 'Icon for the stop forwarding action.')); export const openBrowserIcon = registerIcon('ports-open-browser-icon', Codicon.globe, nls.localize('openBrowserIcon', 'Icon for the open browser action.')); +export const openPreviewIcon = registerIcon('ports-open-preview-icon', Codicon.openPreview, nls.localize('openPreviewIcon', 'Icon for the open preview action.')); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index d712a510265..05cb916dfbf 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -42,7 +42,9 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { forwardPortIcon, openBrowserIcon, portsViewIcon, privatePortIcon, publicPortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; +import { forwardPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, publicPortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; +import { IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); export const PORT_AUTO_FORWARD_SETTING = 'remote.autoForwardPorts'; @@ -959,7 +961,47 @@ export namespace OpenPortInBrowserAction { if (!address.startsWith('http')) { address = `http://${address}`; } - return openerService.open(URI.parse(address)); + return openerService.open(URI.parse(address), { allowContributedOpeners: false }); + } + return Promise.resolve(); + } +} + +export namespace OpenPortInPreviewAction { + export const ID = 'remote.tunnel.openPreview'; + export const LABEL = nls.localize('remote.tunnel.openPreview', "Preview in Editor"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + let key: string | undefined; + if (arg instanceof TunnelItem) { + key = makeAddress(arg.remoteHost, arg.remotePort); + } else if (arg.tunnelRemoteHost && arg.tunnelRemotePort) { + key = makeAddress(arg.tunnelRemoteHost, arg.tunnelRemotePort); + } + if (key) { + const model = accessor.get(IRemoteExplorerService).tunnelModel; + const openerService = accessor.get(IOpenerService); + const externalOpenerService = accessor.get(IExternalUriOpenerService); + return run(model, openerService, externalOpenerService, key); + } + }; + } + + export async function run(model: TunnelModel, openerService: IOpenerService, externalOpenerService: IExternalUriOpenerService, key: string) { + const tunnel = model.forwarded.get(key) || model.detected.get(key); + let address: string | undefined; + if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remoteHost, tunnel.remotePort))) { + if (!address.startsWith('http')) { + address = `http://${address}`; + } + const uri = URI.parse(address); + const sourceUri = URI.parse(`http://${tunnel.remoteHost}:${tunnel.remotePort}`); + const opener = await externalOpenerService.getOpener(uri, { sourceUri }, new CancellationTokenSource().token); + if (opener) { + return opener.openExternalUri(uri, { sourceUri }, new CancellationTokenSource().token); + } + return openerService.open(sourceUri); } return Promise.resolve(); } @@ -1145,6 +1187,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand(ClosePortAction.COMMANDPALETTE_ID, ClosePortAction.commandPaletteHandler()); CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler()); +CommandsRegistry.registerCommand(OpenPortInPreviewAction.ID, OpenPortInPreviewAction.handler()); CommandsRegistry.registerCommand(OpenPortInBrowserCommandPaletteAction.ID, OpenPortInBrowserCommandPaletteAction.handler()); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CopyAddressAction.INLINE_ID, @@ -1226,6 +1269,15 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', order: 2, + command: { + id: OpenPortInPreviewAction.ID, + title: OpenPortInPreviewAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Detected)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 3, command: { id: LabelTunnelAction.ID, title: LabelTunnelAction.LABEL, @@ -1287,6 +1339,15 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ }, when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Detected)) })); +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 1, + command: { + id: OpenPortInPreviewAction.ID, + title: OpenPortInPreviewAction.LABEL, + icon: openPreviewIcon + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Detected)) +})); MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ order: 0, command: { diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index f8ff86aea20..c19433661d3 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -143,10 +143,11 @@ Registry.as(ConfigurationExtensions.Configuration) properties: { 'onAutoForward': { type: 'string', - enum: ['notify', 'openBrowser', 'silent', 'ignore'], + enum: ['notify', 'openBrowser', 'openPreview', 'silent', 'ignore'], enumDescriptions: [ localize('remote.portsAttributes.notify', "Shows a notification when a port is automatically forwarded."), localize('remote.portsAttributes.openBrowser', "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser."), + localize('remote.portsAttributes.openPreview', "Opens a preview in the same window when the port is automatically forwarded."), localize('remote.portsAttributes.silent', "Shows no notification and takes no action when this port is automatically forwarded."), localize('remote.portsAttributes.ignore', "This port will not be automatically forwarded.") ], diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 25f3bd37cd8..a6025446821 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -50,8 +50,7 @@ const viewContainer = Registry.as(ViewContainerExtensio alwaysUseContainerInfo: true, order: 2, hideIfEmpty: true, - commandActionDescriptor: false, -}, ViewContainerLocation.Sidebar); +}, ViewContainerLocation.Sidebar, { donotRegisterOpenCommand: true }); const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -70,7 +69,7 @@ viewsRegistry.registerViews([{ weight: 80, order: -999, containerIcon: sourceControlViewIcon, - commandActionDescriptor: { + openCommandActionDescriptor: { id: viewContainer.id, mnemonicTitle: localize({ key: 'miViewSCM', comment: ['&& denotes a mnemonic'] }, "S&&CM"), keybindings: { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 41164bb0c0d..ec8c2afce05 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -623,8 +623,7 @@ const viewContainer = Registry.as(ViewExtensions.ViewCo hideIfEmpty: true, icon: searchViewIcon, order: 1, - commandActionDescriptor: false, -}, ViewContainerLocation.Sidebar); +}, ViewContainerLocation.Sidebar, { donotRegisterOpenCommand: true }); const viewDescriptor: IViewDescriptor = { id: VIEW_ID, @@ -633,7 +632,7 @@ const viewDescriptor: IViewDescriptor = { ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true, - commandActionDescriptor: { + openCommandActionDescriptor: { id: viewContainer.id, mnemonicTitle: nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"), keybindings: { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index d938abe46af..772c2e84482 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -28,7 +28,7 @@ import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/cont import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import schemaVersion1 from '../common/jsonSchema_v1'; -import schemaVersion2, { updateProblemMatchers } from '../common/jsonSchema_v2'; +import schemaVersion2, { updateProblemMatchers, updateTaskDefinitions } from '../common/jsonSchema_v2'; import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -36,6 +36,7 @@ import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { TasksQuickAccessProvider } from 'vs/workbench/contrib/tasks/browser/tasksQuickAccess'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.or(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); @@ -415,6 +416,11 @@ ProblemMatcherRegistry.onMatcherChanged(() => { jsonRegistry.notifySchemaChanged(tasksSchemaId); }); +TaskDefinitionRegistry.onDefinitionsChanged(() => { + updateTaskDefinitions(); + jsonRegistry.notifySchemaChanged(tasksSchemaId); +}); + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ id: 'task', diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 440ab712fbf..fda80c732c9 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -383,7 +383,18 @@ let taskConfiguration: IJSONSchema = { let taskDefinitions: IJSONSchema[] = []; TaskDefinitionRegistry.onReady().then(() => { + updateTaskDefinitions(); +}); + +export function updateTaskDefinitions() { for (let taskType of TaskDefinitionRegistry.all()) { + // Check that we haven't already added this task type + if (taskDefinitions.find(schema => { + return schema.properties?.type?.enum?.find ? schema.properties?.type.enum.find(element => element === taskType.taskType) : undefined; + })) { + continue; + } + let schema: IJSONSchema = Objects.deepClone(taskConfiguration); const schemaProperties = schema.properties!; // Since we do this after the schema is assigned we need to patch the refs. @@ -408,7 +419,7 @@ TaskDefinitionRegistry.onReady().then(() => { fixReferences(schema); taskDefinitions.push(schema); } -}); +} let customize = Objects.deepClone(taskConfiguration); customize.properties!.customize = { diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 573a3c7a2d0..a3b89c01dc8 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -14,6 +14,7 @@ import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/serv import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Emitter, Event } from 'vs/base/common/event'; const taskDefinitionSchema: IJSONSchema = { @@ -95,6 +96,7 @@ export interface ITaskDefinitionRegistry { get(key: string): Tasks.TaskDefinition; all(): Tasks.TaskDefinition[]; getJsonSchema(): IJSONSchema; + onDefinitionsChanged: Event; } class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { @@ -102,6 +104,8 @@ class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { private taskTypes: IStringDictionary; private readyPromise: Promise; private _schema: IJSONSchema | undefined; + private _onDefinitionsChanged: Emitter = new Emitter(); + public onDefinitionsChanged: Event = this._onDefinitionsChanged.event; constructor() { this.taskTypes = Object.create(null); @@ -125,6 +129,9 @@ class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { } } } + if ((delta.removed.length > 0) || (delta.added.length > 0)) { + this._onDefinitionsChanged.fire(); + } } catch (error) { } resolve(undefined); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 955296c8224..97e41c87da7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -69,8 +69,7 @@ const VIEW_CONTAINER = Registry.as(ViewContainerExtensi storageId: TERMINAL_VIEW_ID, hideIfEmpty: true, order: 3, - commandActionDescriptor: false, -}, ViewContainerLocation.Panel); +}, ViewContainerLocation.Panel, { donotRegisterOpenCommand: true }); Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_VIEW_ID); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: TERMINAL_VIEW_ID, @@ -79,7 +78,7 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(TerminalViewPane), - commandActionDescriptor: { + openCommandActionDescriptor: { id: TERMINAL_COMMAND_ID.TOGGLE, mnemonicTitle: nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), keybindings: { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index c4a198c99a5..a8ee0aa6135 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -272,14 +272,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local); } - const extensionKinds = getExtensionKind(manifest, this.productService, this.configurationService); - if (this.extensionManagementServerService.remoteExtensionManagementServer) { - const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension because it is declared as {1} extension kind and does not run in this setup.", gallery.displayName || gallery.name, extensionKinds.map(e => `"${e}"`).join(', '))); - error.name = INSTALL_ERROR_NOT_SUPPORTED; - return Promise.reject(error); - } - - const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension because it is declared as {1} extension kind and does not run in this setup.", gallery.displayName || gallery.name, extensionKinds.map(e => `"${e}"`).join(', '))); + const error = new Error(localize('cannot be installed', "Cannot install the '{0}' extension because it is declared to not run in this setup.", gallery.displayName || gallery.name)); error.name = INSTALL_ERROR_NOT_SUPPORTED; return Promise.reject(error); } diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 199ea6e15ef..a9fef74ecec 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -60,8 +60,13 @@ export interface IRPCProtocolLogger { const noop = () => { }; +const _RPCProtocolSymbol = Symbol.for('rpcProtocol'); +const _RPCProxySymbol = Symbol.for('rpcProxy'); + export class RPCProtocol extends Disposable implements IRPCProtocol { + [_RPCProtocolSymbol] = true; + private static readonly UNRESPONSIVE_TIME = 3 * 1000; // 3s private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); @@ -182,14 +187,14 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { } public getProxy(identifier: ProxyIdentifier): T { - const rpcId = identifier.nid; + const { nid: rpcId, sid } = identifier; if (!this._proxies[rpcId]) { - this._proxies[rpcId] = this._createProxy(rpcId); + this._proxies[rpcId] = this._createProxy(rpcId, sid); } return this._proxies[rpcId]; } - private _createProxy(rpcId: number): T { + private _createProxy(rpcId: number, debugName: string): T { let handler = { get: (target: any, name: PropertyKey) => { if (typeof name === 'string' && !target[name] && name.charCodeAt(0) === CharCode.DollarSign) { @@ -197,6 +202,9 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { return this._remoteCall(rpcId, name, myArgs); }; } + if (name === _RPCProxySymbol) { + return debugName; + } return target[name]; } }; diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 0242eaf1d07..64d3bcb3f42 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -118,6 +118,7 @@ export function mapHasAddressLocalhostOrAllInterfaces(map: Map, ho export enum OnPortForward { Notify = 'notify', OpenBrowser = 'openBrowser', + OpenPreview = 'openPreview', Silent = 'silent', Ignore = 'ignore' } diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index a742ef1a517..e00e3f50978 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -463,11 +463,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const container = this.viewContainersRegistry.registerViewContainer({ id, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), - title: 'Custom Views', // we don't want to see this, so no need to localize + title: id, // we don't want to see this so using id icon: location === ViewContainerLocation.Sidebar ? defaultViewIcon : undefined, storageId: getViewContainerStorageId(id), hideIfEmpty: true - }, location); + }, location, { donotRegisterOpenCommand: true }); const cachedInfo = this.cachedViewContainerInfo.get(container.id); if (cachedInfo !== location) { diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index 9a3fcf1554d..51a406117d8 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -359,7 +359,7 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode iconChanged = true; } - const keybindingId = this.viewContainer.commandActionDescriptor === false ? this.activeViewDescriptors.find(v => v.commandActionDescriptor)?.commandActionDescriptor?.id : (this.viewContainer.commandActionDescriptor?.id ?? this.viewContainer.id); + const keybindingId = this.viewContainer.openCommandActionDescriptor?.id ?? this.activeViewDescriptors.find(v => v.openCommandActionDescriptor)?.openCommandActionDescriptor?.id; let keybindingIdChanged: boolean = false; if (this._keybindingId !== keybindingId) { this._keybindingId = keybindingId; diff --git a/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts b/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts index 6e59c522f2a..ca9a7f7999d 100644 --- a/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts @@ -11,6 +11,7 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { NullLogService } from 'vs/platform/log/common/log'; +import { Lazy } from 'vs/base/common/lazy'; suite('ExtHostTextEditor', () => { @@ -20,21 +21,21 @@ suite('ExtHostTextEditor', () => { ], '\n', 1, 'text', false); setup(() => { - editor = new ExtHostTextEditor('fake', null!, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); + editor = new ExtHostTextEditor('fake', null!, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); }); test('disposed editor', () => { - assert.ok(editor.document); + assert.ok(editor.value.document); editor._acceptViewColumn(3); - assert.strictEqual(3, editor.viewColumn); + assert.strictEqual(3, editor.value.viewColumn); editor.dispose(); assert.throws(() => editor._acceptViewColumn(2)); - assert.strictEqual(3, editor.viewColumn); + assert.strictEqual(3, editor.value.viewColumn); - assert.ok(editor.document); + assert.ok(editor.value.document); assert.throws(() => editor._acceptOptions(null!)); assert.throws(() => editor._acceptSelections([])); }); @@ -47,15 +48,15 @@ suite('ExtHostTextEditor', () => { applyCount += 1; return Promise.resolve(true); } - }, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); + }, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); - await editor.edit(edit => { }); + await editor.value.edit(edit => { }); assert.strictEqual(applyCount, 0); - await editor.edit(edit => { edit.setEndOfLine(1); }); + await editor.value.edit(edit => { edit.setEndOfLine(1); }); assert.strictEqual(applyCount, 1); - await editor.edit(edit => { edit.delete(new Range(0, 0, 1, 1)); }); + await editor.value.edit(edit => { edit.delete(new Range(0, 0, 1, 1)); }); assert.strictEqual(applyCount, 2); }); }); @@ -89,7 +90,6 @@ suite('ExtHostTextEditorOptions', () => { }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -103,20 +103,18 @@ suite('ExtHostTextEditorOptions', () => { function assertState(opts: ExtHostTextEditorOptions, expected: IResolvedTextEditorConfiguration): void { let actual = { - tabSize: opts.tabSize, - indentSize: opts.indentSize, - insertSpaces: opts.insertSpaces, - cursorStyle: opts.cursorStyle, - lineNumbers: opts.lineNumbers + tabSize: opts.value.tabSize, + insertSpaces: opts.value.insertSpaces, + cursorStyle: opts.value.cursorStyle, + lineNumbers: opts.value.lineNumbers }; assert.deepStrictEqual(actual, expected); } test('can set tabSize to the same value', () => { - opts.tabSize = 4; + opts.value.tabSize = 4; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -125,10 +123,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can change tabSize to positive integer', () => { - opts.tabSize = 1; + opts.value.tabSize = 1; assertState(opts, { tabSize: 1, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -137,10 +134,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can change tabSize to positive float', () => { - opts.tabSize = 2.3; + opts.value.tabSize = 2.3; assertState(opts, { tabSize: 2, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -149,10 +145,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can change tabSize to a string number', () => { - opts.tabSize = '2'; + opts.value.tabSize = '2'; assertState(opts, { tabSize: 2, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -161,10 +156,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('tabSize can request indentation detection', () => { - opts.tabSize = 'auto'; + opts.value.tabSize = 'auto'; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -173,10 +167,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('ignores invalid tabSize 1', () => { - opts.tabSize = null!; + opts.value.tabSize = null!; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -185,10 +178,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('ignores invalid tabSize 2', () => { - opts.tabSize = -5; + opts.value.tabSize = -5; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -197,10 +189,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('ignores invalid tabSize 3', () => { - opts.tabSize = 'hello'; + opts.value.tabSize = 'hello'; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -209,130 +200,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('ignores invalid tabSize 4', () => { - opts.tabSize = '-17'; + opts.value.tabSize = '-17'; assertState(opts, { tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, []); - }); - - test('can set indentSize to the same value', () => { - opts.indentSize = 4; - assertState(opts, { - tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, []); - }); - - test('can change indentSize to positive integer', () => { - opts.indentSize = 1; - assertState(opts, { - tabSize: 4, - indentSize: 1, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, [{ indentSize: 1 }]); - }); - - test('can change indentSize to positive float', () => { - opts.indentSize = 2.3; - assertState(opts, { - tabSize: 4, - indentSize: 2, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, [{ indentSize: 2 }]); - }); - - test('can change indentSize to a string number', () => { - opts.indentSize = '2'; - assertState(opts, { - tabSize: 4, - indentSize: 2, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, [{ indentSize: 2 }]); - }); - - test('indentSize can request to use tabSize', () => { - opts.indentSize = 'tabSize'; - assertState(opts, { - tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, [{ indentSize: 'tabSize' }]); - }); - - test('indentSize cannot request indentation detection', () => { - opts.indentSize = 'auto'; - assertState(opts, { - tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, []); - }); - - test('ignores invalid indentSize 1', () => { - opts.indentSize = null!; - assertState(opts, { - tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, []); - }); - - test('ignores invalid indentSize 2', () => { - opts.indentSize = -5; - assertState(opts, { - tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, []); - }); - - test('ignores invalid indentSize 3', () => { - opts.indentSize = 'hello'; - assertState(opts, { - tabSize: 4, - indentSize: 4, - insertSpaces: false, - cursorStyle: TextEditorCursorStyle.Line, - lineNumbers: RenderLineNumbersType.On - }); - assert.deepStrictEqual(calls, []); - }); - - test('ignores invalid indentSize 4', () => { - opts.indentSize = '-17'; - assertState(opts, { - tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -341,10 +211,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can set insertSpaces to the same value', () => { - opts.insertSpaces = false; + opts.value.insertSpaces = false; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -353,10 +222,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can set insertSpaces to boolean', () => { - opts.insertSpaces = true; + opts.value.insertSpaces = true; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -365,10 +233,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can set insertSpaces to false string', () => { - opts.insertSpaces = 'false'; + opts.value.insertSpaces = 'false'; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -377,10 +244,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can set insertSpaces to truey', () => { - opts.insertSpaces = 'hello'; + opts.value.insertSpaces = 'hello'; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -389,10 +255,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('insertSpaces can request indentation detection', () => { - opts.insertSpaces = 'auto'; + opts.value.insertSpaces = 'auto'; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -401,10 +266,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can set cursorStyle to same value', () => { - opts.cursorStyle = TextEditorCursorStyle.Line; + opts.value.cursorStyle = TextEditorCursorStyle.Line; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -413,10 +277,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can change cursorStyle', () => { - opts.cursorStyle = TextEditorCursorStyle.Block; + opts.value.cursorStyle = TextEditorCursorStyle.Block; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: RenderLineNumbersType.On @@ -425,10 +288,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can set lineNumbers to same value', () => { - opts.lineNumbers = TextEditorLineNumbersStyle.On; + opts.value.lineNumbers = TextEditorLineNumbersStyle.On; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -437,10 +299,9 @@ suite('ExtHostTextEditorOptions', () => { }); test('can change lineNumbers', () => { - opts.lineNumbers = TextEditorLineNumbersStyle.Off; + opts.value.lineNumbers = TextEditorLineNumbersStyle.Off; assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.Off @@ -457,7 +318,6 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -472,7 +332,6 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -487,7 +346,6 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 3, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -502,7 +360,6 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, - indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: RenderLineNumbersType.Relative