diff --git a/.eslintrc.json b/.eslintrc.json index 7e6b386b804..c28784041b4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -233,6 +233,7 @@ "electron", "events", "fs", + "fs/promises", "graceful-fs", "http", "https", @@ -244,6 +245,7 @@ "os", "path", "perf_hooks", + "readline", "stream", "string_decoder", "tas-client-umd", diff --git a/.yarnrc b/.yarnrc index 2e1d8081976..37b969922e9 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,4 +1,5 @@ disturl "https://electronjs.org/headers" -target "22.3.10" +target "22.3.11" +ms_build_id "21658788" runtime "electron" build_from_source "true" diff --git a/build/checksums/nodejs.txt b/build/checksums/nodejs.txt index edc1af90cd9..5229a5bc80a 100644 --- a/build/checksums/nodejs.txt +++ b/build/checksums/nodejs.txt @@ -1,34 +1,7 @@ -dfb37570ef34ac04f34c26d0ec558df60a9665df5961c01c1657c0ca495f2f01 node-v16.17.1-aix-ppc64.tar.gz f9f02f7872e2e8ee54320fce13deb9d56904f32bb0615b6e21aa3371d8899150 node-v16.17.1-darwin-arm64.tar.gz -09a45f60bfb9dfbea4f69044dc733ef983945acd92ca89ccccac267f3d71bd44 node-v16.17.1-darwin-arm64.tar.xz 3db26761ad8493b894d42260d7e65094b7af9bc473588739e61bc1c32d6ff955 node-v16.17.1-darwin-x64.tar.gz -8e7089956fa01cf7d0045945c0863d282dc6818fb0476237c1396497e29a4254 node-v16.17.1-darwin-x64.tar.xz -35ccb95caf02cda3bd680da4350a8ae5d666a7a9eae3afe5c2a1b3ef29aef108 node-v16.17.1-headers.tar.gz -554c8d1b4b16e0f4c073b9df7c49c893716a3a533f25ac646f23619f5ccee7df node-v16.17.1-headers.tar.xz adc7032888d4e672a4aac886baede8c04fccdd1a2e7ab4bcf325e3f336f44a3d node-v16.17.1-linux-arm64.tar.gz -3dfb8fd8f6b97df69cdc56524abc906c50ef1d0bf091188616802e6c7c731389 node-v16.17.1-linux-arm64.tar.xz aeab05e35f1d2824ecfb88ca321f1408b44d292b2775f2890972c828e00216d0 node-v16.17.1-linux-armv7l.tar.gz -a035ceefb5e16f5fce98c8ddfdf721b96eec20542c72fb8781bcbb6ef20c5550 node-v16.17.1-linux-armv7l.tar.xz -1f48de7bed99e973c4c50f1b7fc99fc9af5144d093fd6d2b50a1e43b5818bf05 node-v16.17.1-linux-ppc64le.tar.gz -70305934661f89fca64053b85317a75f233d5e3fdb2caa6546a19262a519cf20 node-v16.17.1-linux-ppc64le.tar.xz -029dad48018bda07b481213816549b632059fc673c30fdc7a353e04619128344 node-v16.17.1-linux-s390x.tar.gz -1a47f604944c6aff37cb7483503155671cdb34bda9bfb8962007bc440fa04d77 node-v16.17.1-linux-s390x.tar.xz da5658693243b3ecf6a4cba6751a71df1eb9e9703ca93b42a9404aed85f58ad0 node-v16.17.1-linux-x64.tar.gz -06ba2eb34aa385967f5f58c87a44753f83212f6cccea892b33f80a2e7fda8384 node-v16.17.1-linux-x64.tar.xz -12d10476ea7483298364c810c037b9316d1a73dc8c81cfeff7d794aecadde498 node-v16.17.1.pkg -e423985f6019b2026f9a191adb56a96ae83ecd56cdf839cf94aa980168b7a90f node-v16.17.1.tar.gz -6721feb4152d56d2c6b358ce397abd5a7f1daf09ee2e25c5021b9b4d3f86a330 node-v16.17.1.tar.xz -9777e8c4b2864c5b54a0e4e9400f14887db68560a09b94b4113b560a64d1e680 node-v16.17.1-win-x64.7z -ed290151efb417262b9808a70738d4ab79e9d53653a6a9f4b8dd97912e279dce node-v16.17.1-win-x64.zip -0f8101648d5c9e49e89fee541da9e574f899716c32b7c51a732b1766b9fc4526 node-v16.17.1-win-x86.7z -189b5e8b23226403e7b07a46614de19b444d369e694901e3668e2f549799cbcd node-v16.17.1-win-x86.zip -1bdff65fb7642425c0d6826084d63c4be43520316f0ea0b46e6a51999a0ed7fc node-v16.17.1-x64.msi -b737eb23a2c67c253b9364b5284123faf5220d567615bebd4ec4b81070e4d177 node-v16.17.1-x86.msi f518a70dcab7c3fac5b2e1ef100b4f628edfb160f4fafa9a94ef222da8a6e9ab win-x64/node.exe -2f459a64647db493da63c790ce368ad54f59f086d9f22f59c5018680420197b3 win-x64/node.lib -23215ce7d1e9de9777c3407239e7cf18d29d60f757b772219421ab361ac67c74 win-x64/node_pdb.7z -8e32ec12028fd3e3147435be79a858ed9c870aaafa1fcb291362307ef3c47547 win-x64/node_pdb.zip 2393aff88be19dbe0205cbde4ff0c1d89911b15de5c99c80f6e5e29604eecd12 win-x86/node.exe -5018c3d42f3fbacbd06cb943b3f2696c8e67ca9bdf6864d0e263d6d6911dffd2 win-x86/node.lib -05a4db56444a60ee70b0d2642d7f2d82a33339894d2d73bd07b1a41d6c869e04 win-x86/node_pdb.7z -8f86eacb7f13a1bf6738cb0819d7854a2abca40fc2e9e1f91421e44ba52cad7e win-x86/node_pdb.zip diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 53ac36242aa..1435eb7b5f0 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -125,8 +125,9 @@ const serverWithWebEntryPoints = [ function getNodeVersion() { const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'); - const target = /^target "(.*)"$/m.exec(yarnrc)[1]; - return target; + const nodeVersion = /^target "(.*)"$/m.exec(yarnrc)[1]; + const internalNodeVersion = /^ms_build_id "(.*)"$/m.exec(yarnrc)[1]; + return { nodeVersion, internalNodeVersion }; } function getNodeChecksum(nodeVersion, platform, arch) { @@ -156,7 +157,7 @@ function getNodeChecksum(nodeVersion, platform, arch) { return undefined; } -const nodeVersion = getNodeVersion(); +const { nodeVersion, internalNodeVersion } = getNodeVersion(); BUILD_TARGETS.forEach(({ platform, arch }) => { gulp.task(task.define(`node-${platform}-${arch}`, () => { @@ -193,7 +194,7 @@ function nodejs(platform, arch) { arch = 'x64'; } - log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejs.repository}...`); + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch); @@ -205,14 +206,14 @@ function nodejs(platform, arch) { switch (platform) { case 'win32': - return (product.nodejs.repository !== 'https://nodejs.org' ? - fetchGithub(product.nodejs.repository, { version: product.nodejs.version, name: `win-${arch}-node.exe`, checksumSha256 }) : + return (product.nodejsRepository !== 'https://nodejs.org' ? + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `win-${arch}-node.exe`, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 })) .pipe(rename('node.exe')); case 'darwin': case 'linux': - return (product.nodejs.repository !== 'https://nodejs.org' ? - fetchGithub(product.nodejs.repository, { version: product.nodejs.version, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 }) : + return (product.nodejsRepository !== 'https://nodejs.org' ? + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) diff --git a/build/lib/electron.js b/build/lib/electron.js index 5cf792c491c..1af02cc9c30 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -75,8 +75,10 @@ function darwinBundleDocumentTypes(types, icon) { }; }); } +const { electronVersion, msBuildId } = util.getElectronVersion(); exports.config = { - version: product.electronRepository ? '22.5.7' : util.getElectronVersion(), + version: electronVersion, + tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, productAppName: product.nameLong, companyName: 'Microsoft Corporation', copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', @@ -193,7 +195,7 @@ function getElectron(arch) { }; } async function main(arch = process.arch) { - const version = product.electronRepository ? '22.5.7' : util.getElectronVersion(); + const version = electronVersion; const electronPath = path.join(root, '.build', 'electron'); const versionFile = path.join(electronPath, 'version'); const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; @@ -208,4 +210,4 @@ if (require.main === module) { process.exit(1); }); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsZ0NBQWdDO0FBQ2hDLCtCQUErQjtBQUMvQiw2Q0FBMEM7QUFZMUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZO0lBQ3JDLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxNQUFNLElBQUksR0FBRyxLQUFLLGFBQWEsQ0FBQztBQUMxRixDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFDbkQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBVSxFQUFDLElBQUksQ0FBQyxDQUFDO0FBRWhDLE1BQU0scUJBQXFCLEdBQUcsT0FBTyxDQUFDLGFBQWEsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFbkk7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQVMsd0JBQXdCLENBQUMsVUFBb0IsRUFBRSxJQUFZLEVBQUUsWUFBNEMsRUFBRSxJQUFlO0lBQ2xJLDJGQUEyRjtJQUMzRixJQUFJLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsWUFB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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsZ0NBQWdDO0FBQ2hDLCtCQUErQjtBQUMvQiw2Q0FBMEM7QUFZMUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZO0lBQ3JDLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxNQUFNLElBQUksR0FBRyxLQUFLLGFBQWEsQ0FBQztBQUMxRixDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFDbkQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBVSxFQUFDLElBQUksQ0FBQyxDQUFDO0FBRWhDLE1BQU0scUJBQXFCLEdBQUcsT0FBTyxDQUFDLGFBQWEsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFbkk7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQVMsd0JBQXdCLENBQUMsVUFBb0IsRUFBRSxJQUFZLEVBQUUsWUFBNEMsRUFBRSxJQUFlO0lBQ2xJLDJGQUEyRjtJQUMzRixJQUFJLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsWUFB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 \ No newline at end of file diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 1d11429e586..ea1c3c16086 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -90,8 +90,11 @@ function darwinBundleDocumentTypes(types: { [name: string]: string | string[] }, }); } +const { electronVersion, msBuildId } = util.getElectronVersion(); + export const config = { - version: product.electronRepository ? '22.5.7' : util.getElectronVersion(), + version: electronVersion, + tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, productAppName: product.nameLong, companyName: 'Microsoft Corporation', copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', @@ -212,7 +215,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream { } async function main(arch = process.arch): Promise { - const version = product.electronRepository ? '22.5.7' : util.getElectronVersion(); + const version = electronVersion; const electronPath = path.join(root, '.build', 'electron'); const versionFile = path.join(electronPath, 'version'); const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 0372acdc768..8cbd2d9a8f4 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -312,6 +312,7 @@ "--vscode-inputValidation-warningBackground", "--vscode-inputValidation-warningBorder", "--vscode-inputValidation-warningForeground", + "--vscode-inlineChat-background", "--vscode-inlineChat-border", "--vscode-inlineChat-regionHighlight", "--vscode-inlineChat-shadow", diff --git a/build/lib/util.js b/build/lib/util.js index 8bd8c1931e8..c6c45010223 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -314,8 +314,9 @@ function streamToPromise(stream) { exports.streamToPromise = streamToPromise; function getElectronVersion() { const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); - const target = /^target "(.*)"$/m.exec(yarnrc)[1]; - return target; + const electronVersion = /^target "(.*)"$/m.exec(yarnrc)[1]; + const msBuildId = /^ms_build_id "(.*)"$/m.exec(yarnrc)[1]; + return { electronVersion, msBuildId }; } exports.getElectronVersion = getElectronVersion; function acquireWebNodePaths() { @@ -405,4 +406,4 @@ function buildWebNodePaths(outDir) { return result; } exports.buildWebNodePaths = buildWebNodePaths; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInV0aWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsbUNBQW1DO0FBQ25DLHNDQUF1QztBQUN2Qyx1Q0FBdUM7QUFDdkMsc0NBQXNDO0FBQ3RDLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsa0NBQWtDO0FBQ2xDLG1DQUFtQztBQUduQyw2QkFBb0M7QUFDcEMsZ0RBQWdEO0FBRWhELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBTW5ELE1BQU0sbUJBQW1CLEdBQXVCLEVBQUUsdUJBQXVCLEVBQUUsR0FBRyxFQUFFLENBQUMsS0FBSyxFQUFFLENBQUM7QUFNekYsU0FBZ0IsV0FBVyxDQUFDLGNBQStCLEVBQUUsT0FBK0IsRUFBRSxvQkFBOEI7SUFDM0gsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQzNCLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUM1QixJQUFJLEtBQUssR0FBRyxNQUFNLENBQUM7SUFDbkIsSUFBSSxNQUFNLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUVqQyxNQUFNLEtBQUssR0FBbUMsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLHVCQUF1QixFQUFFLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO0lBRXBKLE1BQU0sR0FBRyxHQUFHLENBQUMsS0FBNkIsRUFBRSxhQUFzQixFQUFFLEVBQUU7UUFDckUsS0FBSyxHQUFHLFNBQVMsQ0FBQztRQUVsQixNQUFNLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLENB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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInV0aWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsbUNBQW1DO0FBQ25DLHNDQUF1QztBQUN2Qyx1Q0FBdUM7QUFDdkMsc0NBQXNDO0FBQ3RDLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsa0NBQWtDO0FBQ2xDLG1DQUFtQztBQUduQyw2QkFBb0M7QUFDcEMsZ0RBQWdEO0FBRWhELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBTW5ELE1BQU0sbUJBQW1CLEdBQXVCLEVBQUUsdUJBQXVCLEVBQUUsR0FBRyxFQUFFLENBQUMsS0FBSyxFQUFFLENBQUM7QUFNekYsU0FBZ0IsV0FBVyxDQUFDLGNBQStCLEVBQUUsT0FBK0IsRUFBRSxvQkFBOEI7SUFDM0gsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQzNCLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUM1QixJQUFJLEtBQUssR0FBRyxNQUFNLENBQUM7SUFDbkIsSUFBSSxNQUFNLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUVqQyxNQUFNLEtBQUssR0FBbUMsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLHVCQUF1QixFQUFFLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO0lBRXBKLE1BQU0sR0FBRyxHQUFHLENBQUMsS0FBNkIsRUFBRSxhQUFzQixFQUFFLEVBQUU7UUFDckUsS0FBSyxHQUFHLFNBQVMsQ0FBQztRQUVsQixNQUFNLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLENB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 \ No newline at end of file diff --git a/build/lib/util.ts b/build/lib/util.ts index 1a0edc582f2..b0f9c054997 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -384,10 +384,11 @@ export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise { }); } -export function getElectronVersion(): string { +export function getElectronVersion(): Record { const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); - const target = /^target "(.*)"$/m.exec(yarnrc)![1]; - return target; + const electronVersion = /^target "(.*)"$/m.exec(yarnrc)![1]; + const msBuildId = /^ms_build_id "(.*)"$/m.exec(yarnrc)![1]; + return { electronVersion, msBuildId }; } export function acquireWebNodePaths() { diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index f249ad032cb..0197f5f2571 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -30,7 +30,7 @@ function getSha(filename) { return hash.digest('hex'); } async function getSysroot(arch) { - const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion()}/script/sysroots.json`; + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion().electronVersion}/script/sysroots.json`; const sysrootDictLocation = `${(0, os_1.tmpdir)()}/sysroots.json`; const result = (0, child_process_1.spawnSync)('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); if (result.status !== 0) { @@ -87,4 +87,4 @@ async function getSysroot(arch) { return sysroot; } exports.getSysroot = getSysroot; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdGFsbC1zeXNyb290LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaW5zdGFsbC1zeXNyb290LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGlEQUEwQztBQUMxQyxtQ0FBb0M7QUFDcEMsMkJBQTRCO0FBQzVCLHlCQUF5QjtBQUN6QiwrQkFBK0I7QUFDL0IsNkJBQTZCO0FBRTdCLHVDQUF1QztBQUV2QyxvSEFBb0g7QUFDcEgsTUFBTSxVQUFVLEdBQUcsNENBQTRDLENBQUM7QUFDaEUsTUFBTSxRQUFRLEdBQUcsb0JBQW9CLENBQUM7QUFFdEMsU0FBUyxNQUFNLENBQUMsUUFBcUI7SUFDcEMsTUFBTSxJQUFJLEdBQUcsSUFBQSxtQkFBVSxFQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hDLDJCQUEyQjtJQUMzQixNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUN0QyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsQ0FBQztJQUN6QyxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFDO0lBQ2xCLE9BQU8sQ0FBQyxTQUFTLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLEtBQUssTUFBTSxDQUFDLE1BQU0sRUFBRTtRQUMzRixJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BCLFFBQVEsSUFBSSxTQUFTLENBQUM7S0FDdEI7SUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7SUFDeEMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFD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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdGFsbC1zeXNyb290LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaW5zdGFsbC1zeXNyb290LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGlEQUEwQztBQUMxQyxtQ0FBb0M7QUFDcEMsMkJBQTRCO0FBQzVCLHlCQUF5QjtBQUN6QiwrQkFBK0I7QUFDL0IsNkJBQTZCO0FBRTdCLHVDQUF1QztBQUV2QyxvSEFBb0g7QUFDcEgsTUFBTSxVQUFVLEdBQUcsNENBQTRDLENBQUM7QUFDaEUsTUFBTSxRQUFRLEdBQUcsb0JBQW9CLENBQUM7QUFFdEMsU0FBUyxNQUFNLENBQUMsUUFBcUI7SUFDcEMsTUFBTSxJQUFJLEdBQUcsSUFBQSxtQkFBVSxFQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hDLDJCQUEyQjtJQUMzQixNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUN0QyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsQ0FBQztJQUN6QyxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFDO0lBQ2xCLE9BQU8sQ0FBQyxTQUFTLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLEtBQUssTUFBTSxDQUFDLE1BQU0sRUFBRTtRQUMzRixJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BCLFFBQVEsSUFBSSxTQUFTLENBQUM7S0FDdEI7SUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7SUFDeEMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFD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 \ No newline at end of file diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index ac9de5b8578..49eb4a67127 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -38,7 +38,7 @@ type SysrootDictEntry = { }; export async function getSysroot(arch: DebianArchString): Promise { - const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion()}/script/sysroots.json`; + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion().electronVersion}/script/sysroots.json`; const sysrootDictLocation = `${tmpdir()}/sysroots.json`; const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); if (result.status !== 0) { diff --git a/cgmanifest.json b/cgmanifest.json index 761741d5ec1..41afe464f7e 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -533,7 +533,7 @@ }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "22.3.10" + "version": "22.3.11" }, { "component": { diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 92decf5ff0e..5d23bd634fb 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -6,7 +6,7 @@ use std::{fmt, path::PathBuf}; use crate::{constants, log, options, tunnels::code_server::CodeServerArgs}; -use clap::{ValueEnum, Args, Parser, Subcommand}; +use clap::{Args, Parser, Subcommand, ValueEnum}; use const_format::concatcp; const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI"); diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index b8d7b0b60bd..d11c15ef1e3 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ use async_trait::async_trait; -use base64::{Engine as _, engine::general_purpose as b64}; +use base64::{engine::general_purpose as b64, Engine as _}; +use serde::Serialize; use sha2::{Digest, Sha256}; use std::{str::FromStr, time::Duration}; use sysinfo::Pid; @@ -247,8 +248,14 @@ pub async fn kill(ctx: CommandContext) -> Result { .map_err(|e| e.into()) } +#[derive(Serialize)] +pub struct StatusOutput { + pub tunnel: Option, + pub service_installed: bool, +} + pub async fn status(ctx: CommandContext) -> Result { - let status = do_single_rpc_call::<_, protocol::singleton::Status>( + let tunnel_status = do_single_rpc_call::<_, protocol::singleton::Status>( &ctx.paths.tunnel_lockfile(), ctx.log.clone(), protocol::singleton::METHOD_STATUS, @@ -256,17 +263,24 @@ pub async fn status(ctx: CommandContext) -> Result { ) .await; - match status { - Err(CodeError::NoRunningTunnel) => { - ctx.log.result(CodeError::NoRunningTunnel.to_string()); - Ok(1) - } - Err(e) => Err(e.into()), - Ok(s) => { - ctx.log.result(serde_json::to_string(&s).unwrap()); - Ok(0) - } - } + let service_installed = create_service_manager(ctx.log.clone(), &ctx.paths) + .is_installed() + .await + .unwrap_or(false); + + ctx.log.result( + serde_json::to_string(&StatusOutput { + service_installed, + tunnel: match tunnel_status { + Ok(s) => Some(s.tunnel), + Err(CodeError::NoRunningTunnel) => None, + Err(e) => return Err(e.into()), + }, + }) + .unwrap(), + ); + + Ok(0) } /// Removes unused servers. @@ -331,6 +345,13 @@ async fn serve_with_csa( log = log.tee(log_broadcast.clone()); log::install_global_logger(log.clone()); // re-install so that library logs are captured + debug!( + log, + "Starting tunnel with `{} {}`", + APPLICATION_NAME, + std::env::args().collect::>().join(" ") + ); + // Intentionally read before starting the server. If the server updated and // respawn is requested, the old binary will get renamed, and then // current_exe will point to the wrong path. @@ -421,7 +442,10 @@ async fn serve_with_csa( return Ok(exit.code().unwrap_or(1)); } - Next::Exit => return Ok(0), + Next::Exit => { + debug!(log, "Tunnel shut down"); + return Ok(0); + } Next::Restart => continue, } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 93777852df2..b2e23cb4d69 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -18,8 +18,8 @@ pub mod tunnels; pub mod update_service; pub mod util; -mod download_cache; mod async_pipe; +mod download_cache; mod json_rpc; mod msgpack_rpc; mod rpc; diff --git a/cli/src/log.rs b/cli/src/log.rs index 1cd908c4b6c..a7561a37f6c 100644 --- a/cli/src/log.rs +++ b/cli/src/log.rs @@ -159,9 +159,21 @@ pub struct FileLogSink { file: Arc>, } +const FILE_LOG_SIZE_LIMIT: u64 = 1024 * 1024 * 10; // 10MB + impl FileLogSink { pub fn new(level: Level, path: &Path) -> std::io::Result { - let file = std::fs::File::create(path)?; + // Truncate the service log occasionally to avoid growing infinitely + if matches!(path.metadata(), Ok(m) if m.len() > FILE_LOG_SIZE_LIMIT) { + // ignore errors, can happen if another process is writing right now + let _ = std::fs::remove_file(path); + } + + let file = std::fs::OpenOptions::new() + .append(true) + .create(true) + .open(path)?; + Ok(Self { level, file: Arc::new(std::sync::Mutex::new(file)), diff --git a/cli/src/tunnels/service.rs b/cli/src/tunnels/service.rs index 31bf6890996..dba68f3b614 100644 --- a/cli/src/tunnels/service.rs +++ b/cli/src/tunnels/service.rs @@ -41,6 +41,9 @@ pub trait ServiceManager { /// Show logs from the running service to standard out. async fn show_logs(&self) -> Result<(), AnyError>; + /// Gets whether the tunnel service is installed. + async fn is_installed(&self) -> Result; + /// Unregisters the current executable as a service. async fn unregister(&self) -> Result<(), AnyError>; } diff --git a/cli/src/tunnels/service_linux.rs b/cli/src/tunnels/service_linux.rs index 725b72a8d6d..b60d114dc46 100644 --- a/cli/src/tunnels/service_linux.rs +++ b/cli/src/tunnels/service_linux.rs @@ -17,7 +17,7 @@ use crate::{ constants::{APPLICATION_NAME, PRODUCT_NAME_LONG}, log, state::LauncherPaths, - util::errors::{wrap, AnyError}, + util::errors::{wrap, AnyError, DbusConnectFailedError}, }; use super::ServiceManager; @@ -40,7 +40,7 @@ impl SystemdService { async fn connect() -> Result { let connection = Connection::session() .await - .map_err(|e| wrap(e, "error creating dbus session"))?; + .map_err(|e| DbusConnectFailedError(e.to_string()))?; Ok(connection) } @@ -110,9 +110,27 @@ impl ServiceManager for SystemdService { info!(self.log, "Tunnel service successfully started"); + if std::env::var("SSH_CLIENT").is_ok() || std::env::var("SSH_TTY").is_ok() { + info!(self.log, "Tip: run `sudo loginctl enable-linger $USER` to ensure the service stays running after you disconnect."); + } + Ok(()) } + async fn is_installed(&self) -> Result { + let connection = SystemdService::connect().await?; + let proxy = SystemdService::proxy(&connection).await?; + let state = proxy + .get_unit_file_state(SystemdService::service_name_string()) + .await; + + if let Ok(s) = state { + Ok(s == "enabled") + } else { + Ok(false) + } + } + async fn run( self, launcher_paths: crate::state::LauncherPaths, @@ -219,6 +237,8 @@ trait SystemdManagerDbus { force: bool, ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + fn get_unit_file_state(&self, file: String) -> zbus::Result; + fn link_unit_files( &self, files: Vec, diff --git a/cli/src/tunnels/service_macos.rs b/cli/src/tunnels/service_macos.rs index dcc676ffce5..2d0a23f8cb2 100644 --- a/cli/src/tunnels/service_macos.rs +++ b/cli/src/tunnels/service_macos.rs @@ -75,6 +75,11 @@ impl ServiceManager for LaunchdService { handle.run_service(self.log, launcher_paths).await } + async fn is_installed(&self) -> Result { + let cmd = capture_command_and_check_status("launchctl", &["list"]).await?; + Ok(String::from_utf8_lossy(&cmd.stdout).contains(&get_service_label())) + } + async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> { let service_file = get_service_file_path()?; diff --git a/cli/src/tunnels/service_windows.rs b/cli/src/tunnels/service_windows.rs index 3404b4ed52c..427eddd620d 100644 --- a/cli/src/tunnels/service_windows.rs +++ b/cli/src/tunnels/service_windows.rs @@ -114,6 +114,11 @@ impl CliServiceManager for WindowsService { Ok(()) } + async fn is_installed(&self) -> Result { + let key = WindowsService::open_key()?; + Ok(key.get_raw_value(TUNNEL_ACTIVITY_NAME).is_ok()) + } + async fn unregister(&self) -> Result<(), AnyError> { let key = WindowsService::open_key()?; key.delete_value(TUNNEL_ACTIVITY_NAME) diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs index f1c4cbf5c22..6f4630d0c54 100644 --- a/cli/src/util/errors.rs +++ b/cli/src/util/errors.rs @@ -442,6 +442,26 @@ macro_rules! makeAnyError { }; } +#[derive(Debug)] +pub struct DbusConnectFailedError(pub String); + +impl Display for DbusConnectFailedError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut str = String::new(); + str.push_str("Error creating dbus session. This command uses systemd for managing services, you should check that systemd is installed and under your user."); + + if std::env::var("WSL_DISTRO_NAME").is_ok() { + str.push_str("\n\nTo enable systemd on WSL, check out: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/.\n\n"); + } + + str.push_str("If running `systemctl status` works, systemd is ok, but your session dbus may not be. You might need to:\n\n- Install the `dbus-user-session` package, and reboot if it was not installed\n- Start the user dbus session with `systemctl --user enable dbus --now`.\n\nThe error encountered was: "); + str.push_str(&self.0); + str.push('\n'); + + write!(f, "{}", str) + } +} + /// Internal errors in the VS Code CLI. /// Note: other error should be migrated to this type gradually #[derive(Error, Debug)] @@ -522,7 +542,8 @@ makeAnyError!( MissingHomeDirectory, OAuthError, InvalidRpcDataError, - CodeError + CodeError, + DbusConnectFailedError ); impl From for AnyError { diff --git a/cli/src/util/tar.rs b/cli/src/util/tar.rs index 77dd67abbb0..248f63f9720 100644 --- a/cli/src/util/tar.rs +++ b/cli/src/util/tar.rs @@ -6,7 +6,7 @@ use crate::util::errors::{wrap, WrappedError}; use flate2::read::GzDecoder; use std::fs; -use std::io::{Seek, SeekFrom}; +use std::io::Seek; use std::path::{Path, PathBuf}; use tar::Archive; @@ -65,7 +65,7 @@ where // reset since skip logic read the tar already: tar_gz - .seek(SeekFrom::Start(0)) + .rewind() .map_err(|e| wrap(e, "error resetting seek position"))?; let tar = GzDecoder::new(tar_gz); diff --git a/cli/src/util/zipper.rs b/cli/src/util/zipper.rs index 84eec040b0e..0e9939d4db5 100644 --- a/cli/src/util/zipper.rs +++ b/cli/src/util/zipper.rs @@ -88,8 +88,13 @@ where use std::io::Read; use std::os::unix::ffi::OsStringExt; - if matches!(file.unix_mode(), Some(mode) if mode & (S_IFLNK as u32) == (S_IFLNK as u32)) - { + #[cfg(target_os = "macos")] + const S_IFLINK_32: u32 = S_IFLNK as u32; + + #[cfg(target_os = "linux")] + const S_IFLINK_32: u32 = S_IFLNK; + + if matches!(file.unix_mode(), Some(mode) if mode & S_IFLINK_32 == S_IFLINK_32) { let mut link_to = Vec::new(); file.read_to_end(&mut link_to).map_err(|e| { wrap( diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 886d4d8d83d..fdf68a56252 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@emmetio/abbreviation@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.3.2.tgz#375bf6bc6ae6405f62dd0ddab2559b46502d01f4" - integrity sha512-8vqkn4rtjm5Zv34RPgsq3/ij88ri+IcfC2MxPELytrQvfpaLyppscE0YSwDVuIUR6KL5GCBUfr5Mo7SHSbswpA== +"@emmetio/abbreviation@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz#ed2b88fe37b972292d6026c7c540aaf887cecb6e" + integrity sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA== dependencies: - "@emmetio/scanner" "^1.0.3" + "@emmetio/scanner" "^1.0.4" -"@emmetio/css-abbreviation@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.7.tgz#9791269586d780cf4b40078ea79886d1888a188a" - integrity sha512-nrOt3/QROjYYK1cMjoO5fCfHIf0hFpcZeQQt7Ew6ixZ0ElEEs77ijnY57HC6ti91W/mn+c1T7ET8sClBMRHHBg== +"@emmetio/css-abbreviation@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz#b785313486eba6cb7eb623ad39378c4e1063dc00" + integrity sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw== dependencies: - "@emmetio/scanner" "^1.0.3" + "@emmetio/scanner" "^1.0.4" "@emmetio/css-parser@ramya-rao-a/css-parser#vscode": version "0.4.0" @@ -38,11 +38,6 @@ dependencies: "@emmetio/scanner" "^1.0.4" -"@emmetio/scanner@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.3.tgz#755e581517e2302d31a387e4064bf73035ebfc46" - integrity sha512-/EFyTijquAwKMGSBd50RnjxsfDXmZAFp71PGu7sM6LEnEJXMV+FKL7Rvr6YLu4czQmPVRsfyhcbQz+WZnM4AZw== - "@emmetio/scanner@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.4.tgz#e9cdc67194fd91f8b7eb141014be4f2d086c15f1" @@ -64,9 +59,9 @@ integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== "@vscode/emmet-helper@^2.8.8": - version "2.8.8" - resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.8.8.tgz#df64989d2812e031cd6393ce896a2fe33ae976bd" - integrity sha512-QuD4CmNeXSFxuP8VZwI6qL+8vmmd7JcSdwsEIdsrzb4YumWs/+4rXRX9MM+NsFfUO69g6ezngCD7XRd6jY9TQw== + version "2.8.9" + resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.8.9.tgz#536a2cba2f78b0dd25a874b85ab95f9b5424eb39" + integrity sha512-ygpVStaePHt9aI9zk4NNJWI/NsRaeDSW1vQsZVmtpVRVCOdwYlsc3BfB/eppUu1OucT0x3OHDAzKcxnitjcSXQ== dependencies: emmet "^2.4.3" jsonc-parser "^2.3.0" @@ -75,12 +70,12 @@ vscode-uri "^2.1.2" emmet@^2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.4.3.tgz#c99f19e572a270da27f456dd7f65dfda83dc0ec1" - integrity sha512-Bq6zozVDVrLbBmKdosI9Q2DvrFh/ehwnNjgDRsvGVjPOEAhMKie9HwQnPuUi3NOZ2itVGyRwsLAdufnG9DVFwg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.4.4.tgz#801aad64659dc76f3003130db767d77a78ac298e" + integrity sha512-v8Mwpjym55CS3EjJgiCLWUB3J2HSR93jhzXW325720u8KvYxdI2voYLstW3pHBxFz54H6jFjayR9G4LfTG0q+g== dependencies: - "@emmetio/abbreviation" "^2.3.2" - "@emmetio/css-abbreviation" "^2.1.7" + "@emmetio/abbreviation" "^2.3.3" + "@emmetio/css-abbreviation" "^2.1.8" image-size@~1.0.0: version "1.0.0" diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 0b67638d450..9cb2fc14012 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -314,7 +314,7 @@ }, { "match": "(?!when|and|or\\b)\\b([\\w0-9'`^._]+)", - "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", + "comments": "Here we need the \\w modifier in order to check that the words are allowed", "captures": { "1": { "name": "entity.name.type.fsharp" @@ -677,7 +677,7 @@ }, { "match": "(?!with|get|set\\b)\\b([\\w0-9'`^._]+)", - "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", + "comments": "Here we need the \\w modifier in order to check that the words are allowed", "captures": { "1": { "name": "entity.name.type.fsharp" @@ -1658,7 +1658,7 @@ }, { "match": "([\\w0-9'`^._]+)", - "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", + "comments": "Here we need the \\w modifier in order to check that the words are allowed", "captures": { "1": { "name": "entity.name.type.fsharp" @@ -1832,4 +1832,4 @@ ] } } -} \ No newline at end of file +} diff --git a/extensions/github/src/pushErrorHandler.ts b/extensions/github/src/pushErrorHandler.ts index 6d4b4112811..fd0a6782837 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TextDecoder } from 'util'; -import { commands, env, ProgressLocation, Uri, window, workspace, QuickPickOptions, FileType, l10n } from 'vscode'; +import { commands, env, ProgressLocation, Uri, window, workspace, QuickPickOptions, FileType, l10n, Disposable, TextDocumentContentProvider } from 'vscode'; import { getOctokit } from './auth'; import { GitErrorCodes, PushErrorHandler, Remote, Repository } from './typings/git'; import * as path from 'path'; @@ -15,136 +15,6 @@ export function isInCodespaces(): boolean { return env.remoteName === 'codespaces'; } -async function handlePushError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise { - const yes = l10n.t('Create Fork'); - const no = l10n.t('No'); - const askFork = l10n.t('You don\'t have permissions to push to "{0}/{1}" on GitHub. Would you like to create a fork and push to it instead?', owner, repo); - - const answer = await window.showWarningMessage(askFork, { modal: true }, yes, no); - if (answer !== yes) { - return; - } - - const match = /^([^:]*):([^:]*)$/.exec(refspec); - const localName = match ? match[1] : refspec; - let remoteName = match ? match[2] : refspec; - - const [octokit, ghRepository] = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: l10n.t('Create GitHub fork') }, async progress => { - progress.report({ message: l10n.t('Forking "{0}/{1}"...', owner, repo), increment: 33 }); - - const octokit = await getOctokit(); - - type CreateForkResponseData = Awaited>['data']; - - // Issue: what if the repo already exists? - let ghRepository: CreateForkResponseData; - try { - if (isInCodespaces()) { - // Call into the codespaces extension to fork the repository - const resp = await commands.executeCommand<{ repository: CreateForkResponseData; ref: string }>('github.codespaces.forkRepository'); - if (!resp) { - throw new Error('Unable to fork respository'); - } - - ghRepository = resp.repository; - - if (resp.ref) { - let ref = resp.ref; - if (ref.startsWith('refs/heads/')) { - ref = ref.substr(11); - } - - remoteName = ref; - } - } else { - const resp = await octokit.repos.createFork({ owner, repo }); - ghRepository = resp.data; - } - } catch (ex) { - console.error(ex); - throw ex; - } - - progress.report({ message: l10n.t('Pushing changes...'), increment: 33 }); - - // Issue: what if there's already an `upstream` repo? - await repository.renameRemote(remote.name, 'upstream'); - - // Issue: what if there's already another `origin` repo? - const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); - const remoteUrl = protocol === 'https' ? ghRepository.clone_url : ghRepository.ssh_url; - await repository.addRemote('origin', remoteUrl); - - try { - await repository.fetch('origin', remoteName); - await repository.setBranchUpstream(localName, `origin/${remoteName}`); - } catch { - // noop - } - - await repository.push('origin', localName, true); - - return [octokit, ghRepository] as const; - }); - - // yield - (async () => { - const openOnGitHub = l10n.t('Open on GitHub'); - const createPR = l10n.t('Create PR'); - const action = await window.showInformationMessage(l10n.t('The fork "{0}" was successfully created on GitHub.', ghRepository.full_name), openOnGitHub, createPR); - - if (action === openOnGitHub) { - await commands.executeCommand('vscode.open', Uri.parse(ghRepository.html_url)); - } else if (action === createPR) { - const pr = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: l10n.t('Creating GitHub Pull Request...') }, async _ => { - let title = `Update ${remoteName}`; - const head = repository.state.HEAD?.name; - - let body: string | undefined; - - if (head) { - const commit = await repository.getCommit(head); - title = commit.message.split('\n')[0]; - body = commit.message.slice(title.length + 1).trim(); - } - - const templates = await findPullRequestTemplates(repository.rootUri); - if (templates.length > 0) { - templates.sort((a, b) => a.path.localeCompare(b.path)); - - const template = await pickPullRequestTemplate(repository.rootUri, templates); - - if (template) { - body = new TextDecoder('utf-8').decode(await workspace.fs.readFile(template)); - } - } - - const { data: pr } = await octokit.pulls.create({ - owner, - repo, - title, - body, - head: `${ghRepository.owner.login}:${remoteName}`, - base: ghRepository.default_branch - }); - - await repository.setConfig(`branch.${localName}.remote`, 'upstream'); - await repository.setConfig(`branch.${localName}.merge`, `refs/heads/${remoteName}`); - await repository.setConfig(`branch.${localName}.github-pr-owner-number`, `${owner}#${repo}#${pr.number}`); - - return pr; - }); - - const openPR = l10n.t('Open PR'); - const action = await window.showInformationMessage(l10n.t('The PR "{0}/{1}#{2}" was successfully created on GitHub.', owner, repo, pr.number), openPR); - - if (action === openPR) { - await commands.executeCommand('vscode.open', Uri.parse(pr.html_url)); - } - } - })(); -} - const PR_TEMPLATE_FILES = [ { dir: '.', files: ['pull_request_template.md', 'PULL_REQUEST_TEMPLATE.md'] }, { dir: 'docs', files: ['pull_request_template.md', 'PULL_REQUEST_TEMPLATE.md'] }, @@ -207,10 +77,34 @@ export async function pickPullRequestTemplate(repositoryRootUri: Uri, templates: return pickedTemplate?.template; } +class CommandErrorOutputTextDocumentContentProvider implements TextDocumentContentProvider { + + private items = new Map(); + + set(uri: Uri, contents: string): void { + this.items.set(uri.path, contents); + } + + delete(uri: Uri): void { + this.items.delete(uri.path); + } + + provideTextDocumentContent(uri: Uri): string | undefined { + return this.items.get(uri.path); + } +} + export class GithubPushErrorHandler implements PushErrorHandler { - async handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise { - if (error.gitErrorCode !== GitErrorCodes.PermissionDenied) { + private disposables: Disposable[] = []; + private commandErrors = new CommandErrorOutputTextDocumentContentProvider(); + + constructor() { + this.disposables.push(workspace.registerTextDocumentContentProvider('github-output', this.commandErrors)); + } + + async handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { stderr: string; gitErrorCode: GitErrorCodes }): Promise { + if (error.gitErrorCode !== GitErrorCodes.PermissionDenied && error.gitErrorCode !== GitErrorCodes.PushRejected) { return false; } @@ -229,8 +123,175 @@ export class GithubPushErrorHandler implements PushErrorHandler { } const [, owner, repo] = match; - await handlePushError(repository, remote, refspec, owner, repo); - return true; + if (error.gitErrorCode === GitErrorCodes.PermissionDenied) { + await this.handlePermissionDeniedError(repository, remote, refspec, owner, repo); + return true; + } + + // Push protection + if (/GH009: Secrets detected!/i.test(error.stderr)) { + await this.handlePushProtectionError(owner, repo, error.stderr); + return true; + } + + return false; + } + + private async handlePermissionDeniedError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise { + const yes = l10n.t('Create Fork'); + const no = l10n.t('No'); + const askFork = l10n.t('You don\'t have permissions to push to "{0}/{1}" on GitHub. Would you like to create a fork and push to it instead?', owner, repo); + + const answer = await window.showWarningMessage(askFork, { modal: true }, yes, no); + if (answer !== yes) { + return; + } + + const match = /^([^:]*):([^:]*)$/.exec(refspec); + const localName = match ? match[1] : refspec; + let remoteName = match ? match[2] : refspec; + + const [octokit, ghRepository] = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: l10n.t('Create GitHub fork') }, async progress => { + progress.report({ message: l10n.t('Forking "{0}/{1}"...', owner, repo), increment: 33 }); + + const octokit = await getOctokit(); + + type CreateForkResponseData = Awaited>['data']; + + // Issue: what if the repo already exists? + let ghRepository: CreateForkResponseData; + try { + if (isInCodespaces()) { + // Call into the codespaces extension to fork the repository + const resp = await commands.executeCommand<{ repository: CreateForkResponseData; ref: string }>('github.codespaces.forkRepository'); + if (!resp) { + throw new Error('Unable to fork respository'); + } + + ghRepository = resp.repository; + + if (resp.ref) { + let ref = resp.ref; + if (ref.startsWith('refs/heads/')) { + ref = ref.substr(11); + } + + remoteName = ref; + } + } else { + const resp = await octokit.repos.createFork({ owner, repo }); + ghRepository = resp.data; + } + } catch (ex) { + console.error(ex); + throw ex; + } + + progress.report({ message: l10n.t('Pushing changes...'), increment: 33 }); + + // Issue: what if there's already an `upstream` repo? + await repository.renameRemote(remote.name, 'upstream'); + + // Issue: what if there's already another `origin` repo? + const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); + const remoteUrl = protocol === 'https' ? ghRepository.clone_url : ghRepository.ssh_url; + await repository.addRemote('origin', remoteUrl); + + try { + await repository.fetch('origin', remoteName); + await repository.setBranchUpstream(localName, `origin/${remoteName}`); + } catch { + // noop + } + + await repository.push('origin', localName, true); + + return [octokit, ghRepository] as const; + }); + + // yield + (async () => { + const openOnGitHub = l10n.t('Open on GitHub'); + const createPR = l10n.t('Create PR'); + const action = await window.showInformationMessage(l10n.t('The fork "{0}" was successfully created on GitHub.', ghRepository.full_name), openOnGitHub, createPR); + + if (action === openOnGitHub) { + await commands.executeCommand('vscode.open', Uri.parse(ghRepository.html_url)); + } else if (action === createPR) { + const pr = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: l10n.t('Creating GitHub Pull Request...') }, async _ => { + let title = `Update ${remoteName}`; + const head = repository.state.HEAD?.name; + + let body: string | undefined; + + if (head) { + const commit = await repository.getCommit(head); + title = commit.message.split('\n')[0]; + body = commit.message.slice(title.length + 1).trim(); + } + + const templates = await findPullRequestTemplates(repository.rootUri); + if (templates.length > 0) { + templates.sort((a, b) => a.path.localeCompare(b.path)); + + const template = await pickPullRequestTemplate(repository.rootUri, templates); + + if (template) { + body = new TextDecoder('utf-8').decode(await workspace.fs.readFile(template)); + } + } + + const { data: pr } = await octokit.pulls.create({ + owner, + repo, + title, + body, + head: `${ghRepository.owner.login}:${remoteName}`, + base: ghRepository.default_branch + }); + + await repository.setConfig(`branch.${localName}.remote`, 'upstream'); + await repository.setConfig(`branch.${localName}.merge`, `refs/heads/${remoteName}`); + await repository.setConfig(`branch.${localName}.github-pr-owner-number`, `${owner}#${repo}#${pr.number}`); + + return pr; + }); + + const openPR = l10n.t('Open PR'); + const action = await window.showInformationMessage(l10n.t('The PR "{0}/{1}#{2}" was successfully created on GitHub.', owner, repo, pr.number), openPR); + + if (action === openPR) { + await commands.executeCommand('vscode.open', Uri.parse(pr.html_url)); + } + } + })(); + } + + private async handlePushProtectionError(owner: string, repo: string, stderr: string): Promise { + // Open command output in an editor + const timestamp = new Date().getTime(); + const uri = Uri.parse(`github-output:/github-error-${timestamp}`); + this.commandErrors.set(uri, stderr); + + try { + const doc = await workspace.openTextDocument(uri); + await window.showTextDocument(doc); + } + finally { + this.commandErrors.set(uri, stderr); + } + + // Show dialog + const learnMore = l10n.t('Learn More'); + const message = l10n.t('Your push to "{0}/{1}" was rejected by GitHub because push protection is enabled and one or more secrets were detected.', owner, repo); + const answer = await window.showWarningMessage(message, { modal: true }, learnMore); + if (answer === learnMore) { + commands.executeCommand('vscode.open', 'https://aka.ms/vscode-github-push-protection'); + } + } + + dispose() { + this.disposables.forEach(d => d.dispose()); } } diff --git a/extensions/latex/syntaxes/LaTeX.tmLanguage.json b/extensions/latex/syntaxes/LaTeX.tmLanguage.json index 3955e47cb8d..6c2de88ec05 100644 --- a/extensions/latex/syntaxes/LaTeX.tmLanguage.json +++ b/extensions/latex/syntaxes/LaTeX.tmLanguage.json @@ -102,7 +102,7 @@ "name": "punctuation.definition.arguments.begin.latex" } }, - "comment": "this works OK with all kinds of crazy stuff as long as section is one line", + "comment": "this works OK with all kinds of stuff as long as the section is one line", "contentName": "entity.name.section.latex", "end": "\\}", "endCaptures": { @@ -2371,4 +2371,4 @@ ] } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 84d7fe5a5a0..f1f8428adab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.80.0", - "distro": "a44605dbeeed7ad5c41513260004329c4f502793", + "distro": "cbc1aeab0c196a6c6314cf4df21228d9016c70d1", "author": { "name": "Microsoft Corporation" }, @@ -92,14 +92,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.1", - "xterm-addon-canvas": "0.5.0-beta.1", + "xterm": "5.3.0-beta.3", + "xterm-addon-canvas": "0.5.0-beta.2", "xterm-addon-image": "0.4.1", - "xterm-addon-search": "0.13.0-beta.1", - "xterm-addon-serialize": "0.11.0-beta.1", + "xterm-addon-search": "0.13.0-beta.2", + "xterm-addon-serialize": "0.11.0-beta.2", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.1", - "xterm-headless": "5.3.0-beta.1", + "xterm-addon-webgl": "0.16.0-beta.2", + "xterm-headless": "5.3.0-beta.3", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, @@ -132,7 +132,7 @@ "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/experimental-utils": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", - "@vscode/gulp-electron": "^1.34.0", + "@vscode/gulp-electron": "^1.35.0", "@vscode/l10n-dev": "0.0.21", "@vscode/telemetry-extractor": "^1.9.9", "@vscode/test-web": "^0.0.41", @@ -147,7 +147,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "22.3.10", + "electron": "22.3.11", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^39.3.2", diff --git a/product.json b/product.json index 51d2bfe00d6..f055d4c86f4 100644 --- a/product.json +++ b/product.json @@ -30,6 +30,7 @@ "linuxIconName": "code-oss", "licenseFileName": "LICENSE.txt", "reportIssueUrl": "https://github.com/microsoft/vscode/issues/new", + "nodejsRepository": "https://nodejs.org", "urlProtocol": "code-oss", "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/", "builtInExtensions": [ @@ -81,9 +82,5 @@ "publisherDisplayName": "Microsoft" } } - ], - "nodejs": { - "repository": "https://nodejs.org", - "version": "16.17.1" - } + ] } diff --git a/remote/.yarnrc b/remote/.yarnrc index 340ea50f6b3..9c86ae6bba4 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,4 +1,5 @@ disturl "http://nodejs.org/dist" target "16.17.1" +ms_build_id "218982" runtime "node" build_from_source "true" diff --git a/remote/package.json b/remote/package.json index 72ea3dca606..b6f21703ca4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -26,14 +26,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.1", - "xterm-addon-canvas": "0.5.0-beta.1", + "xterm": "5.3.0-beta.3", + "xterm-addon-canvas": "0.5.0-beta.2", "xterm-addon-image": "0.4.1", - "xterm-addon-search": "0.13.0-beta.1", - "xterm-addon-serialize": "0.11.0-beta.1", + "xterm-addon-search": "0.13.0-beta.2", + "xterm-addon-serialize": "0.11.0-beta.2", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.1", - "xterm-headless": "5.3.0-beta.1", + "xterm-addon-webgl": "0.16.0-beta.2", + "xterm-headless": "5.3.0-beta.3", "yauzl": "^2.9.2", "yazl": "^2.4.3" } diff --git a/remote/web/package.json b/remote/web/package.json index fb573e7a0e9..db7baab82d3 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,11 +11,11 @@ "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.1", - "xterm-addon-canvas": "0.5.0-beta.1", + "xterm": "5.3.0-beta.3", + "xterm-addon-canvas": "0.5.0-beta.2", "xterm-addon-image": "0.4.1", - "xterm-addon-search": "0.13.0-beta.1", + "xterm-addon-search": "0.13.0-beta.2", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.1" + "xterm-addon-webgl": "0.16.0-beta.2" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 325be24ac89..2f879c22bf6 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -68,32 +68,32 @@ vscode-textmate@9.0.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== -xterm-addon-canvas@0.5.0-beta.1: - version "0.5.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.1.tgz#b5ae185741423715460a66029c944b4dededfab0" - integrity sha512-A7yjIpyTcOh8ckPJw1YFDvwbTbQ+grM+kTtutOvu5LjLSV9EoCHX17kVoiT2V29ywF7KJMeLYwfBwyFE3uA3QQ== +xterm-addon-canvas@0.5.0-beta.2: + version "0.5.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.2.tgz#1b83c2a9a306766c47a4f80b8c65cc9ee5f5a5c4" + integrity sha512-oTb/2krdbHYGxH2X6yiBZzAB/1WB+apUu4nXHdhBnht20bl8E+YVWqg95D4o0Gl+QJI+XOfB3mqmWaBx1x531A== xterm-addon-image@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.4.1.tgz#ec8f750af48005ad641c1128fa1f551ac198472a" integrity sha512-iJpYyvtbHg4oXSv+D6J73ZfCjnboZpbZ567MLplXDBlYSUknv3kvPTfVMPJATV7Zsx7+bDgyXboCh9vsDf/m/w== -xterm-addon-search@0.13.0-beta.1: - version "0.13.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.1.tgz#9fb6ede402d4c369d59d5d6faefe54a05b125bcf" - integrity sha512-rdOIhwkfRASqTriUO8QP9UY0p6BosLMv1NXTZqhgq3/5xAXx4VZg6mlQjTRGnUz/GJIN1jU9e/Vp20SpocP/Hw== +xterm-addon-search@0.13.0-beta.2: + version "0.13.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.2.tgz#c984a35312acad4ce768d17bc49adffa90eece61" + integrity sha512-+VoPhIRmfiX2uh2t6xD/RJtBYjVjrkNa3dKQnOYEp4UbYzDjK57rZX652mnZ82TQfk/juxf7v+jV5aRdNLZVbA== xterm-addon-unicode11@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.1: - version "0.16.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.1.tgz#e2b41c6b5f838724a5cb3cfa4231e2d1b8f3f130" - integrity sha512-iJK+Uk+23Mh84BNa/44JqAdPESdNKN0ONfw6UztmDk2HTvsy47sU+d/lgF2kOcuI3ew2tRzK9YlupOUhVPwe9g== +xterm-addon-webgl@0.16.0-beta.2: + version "0.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.2.tgz#30489ef235405255ee54077002c90553531870d8" + integrity sha512-DAt4E/QI1w34ToBhcDj0vaZOAHOO+ffwMt2HGDAB7amPXRcMb0LBIjLpyZhB9sD4tbIgsE0vuqZi1R9vKxZwbg== -xterm@5.3.0-beta.1: - version "5.3.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.1.tgz#68e76f2818965592c60bb269360f1fc37219f4ae" - integrity sha512-2v/Qmk1A0wO5oouRWUWZ3wxqtfFjxsQbZ1sWxPGJTQvoTSdkORLG44gxrU+Sk2jB5Ojz+3Kg42bnfFghsXVzlw== +xterm@5.3.0-beta.3: + version "5.3.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.3.tgz#1a1aaf9a57afe4dcf86e87d8dc85e80a41d68644" + integrity sha512-NGxpV25U2W/KKk6M5V2OXuLgrKY+w05ABi66ZEYuCTi7ux1Qv0z+jm7bkgzk1pGGiTVLG+90OGr2nrhbFr5Y4w== diff --git a/remote/yarn.lock b/remote/yarn.lock index b3f8935f2cb..8836ac2d179 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -836,45 +836,45 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-canvas@0.5.0-beta.1: - version "0.5.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.1.tgz#b5ae185741423715460a66029c944b4dededfab0" - integrity sha512-A7yjIpyTcOh8ckPJw1YFDvwbTbQ+grM+kTtutOvu5LjLSV9EoCHX17kVoiT2V29ywF7KJMeLYwfBwyFE3uA3QQ== +xterm-addon-canvas@0.5.0-beta.2: + version "0.5.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.2.tgz#1b83c2a9a306766c47a4f80b8c65cc9ee5f5a5c4" + integrity sha512-oTb/2krdbHYGxH2X6yiBZzAB/1WB+apUu4nXHdhBnht20bl8E+YVWqg95D4o0Gl+QJI+XOfB3mqmWaBx1x531A== xterm-addon-image@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.4.1.tgz#ec8f750af48005ad641c1128fa1f551ac198472a" integrity sha512-iJpYyvtbHg4oXSv+D6J73ZfCjnboZpbZ567MLplXDBlYSUknv3kvPTfVMPJATV7Zsx7+bDgyXboCh9vsDf/m/w== -xterm-addon-search@0.13.0-beta.1: - version "0.13.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.1.tgz#9fb6ede402d4c369d59d5d6faefe54a05b125bcf" - integrity sha512-rdOIhwkfRASqTriUO8QP9UY0p6BosLMv1NXTZqhgq3/5xAXx4VZg6mlQjTRGnUz/GJIN1jU9e/Vp20SpocP/Hw== +xterm-addon-search@0.13.0-beta.2: + version "0.13.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.2.tgz#c984a35312acad4ce768d17bc49adffa90eece61" + integrity sha512-+VoPhIRmfiX2uh2t6xD/RJtBYjVjrkNa3dKQnOYEp4UbYzDjK57rZX652mnZ82TQfk/juxf7v+jV5aRdNLZVbA== -xterm-addon-serialize@0.11.0-beta.1: - version "0.11.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.1.tgz#a4bc1ef5d8b8db0180c07f071ce543536d806db1" - integrity sha512-2I9Dq49nXUc6ymznwJp8SUsDq5owUdYviUy11HzLh35baDjzbG31CCu5Gs8KSlfUxpNRr3BxaV5/hx7MRPu7Qg== +xterm-addon-serialize@0.11.0-beta.2: + version "0.11.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.2.tgz#fff924decfbf1bc08434317894f985fef7bb260b" + integrity sha512-tN4IT2e+EIpsoFpMONUh1OAuoVAcV7AYOLsqMKgH6GNWB1D/LKGo3cwjpw1vwRZzDJJcCcLxYgxlUzhPbDbLxQ== xterm-addon-unicode11@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.1: - version "0.16.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.1.tgz#e2b41c6b5f838724a5cb3cfa4231e2d1b8f3f130" - integrity sha512-iJK+Uk+23Mh84BNa/44JqAdPESdNKN0ONfw6UztmDk2HTvsy47sU+d/lgF2kOcuI3ew2tRzK9YlupOUhVPwe9g== +xterm-addon-webgl@0.16.0-beta.2: + version "0.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.2.tgz#30489ef235405255ee54077002c90553531870d8" + integrity sha512-DAt4E/QI1w34ToBhcDj0vaZOAHOO+ffwMt2HGDAB7amPXRcMb0LBIjLpyZhB9sD4tbIgsE0vuqZi1R9vKxZwbg== -xterm-headless@5.3.0-beta.1: - version "5.3.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.1.tgz#8c7db703b9f57496c2f052411721c00909b08e8b" - integrity sha512-6rsv6l44hLL9Eg2UrfAbCiZcAucdHuPyIsovl2BEmluo4chwd4LD7VINRlPV/x8ML2HgD9SohFyNs5BQAc07Gg== +xterm-headless@5.3.0-beta.3: + version "5.3.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.3.tgz#153cf330082f4b2aae64ff736ef0b62d93c30da8" + integrity sha512-4i/bpFoAn4D4ZA4g8RKrJdhq2EcB1HN2E25yUg3omRbWCOZ2Gp9nAn+62LYzX5rvGqdNbpUTRJLX0lKwEFyLFw== -xterm@5.3.0-beta.1: - version "5.3.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.1.tgz#68e76f2818965592c60bb269360f1fc37219f4ae" - integrity sha512-2v/Qmk1A0wO5oouRWUWZ3wxqtfFjxsQbZ1sWxPGJTQvoTSdkORLG44gxrU+Sk2jB5Ojz+3Kg42bnfFghsXVzlw== +xterm@5.3.0-beta.3: + version "5.3.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.3.tgz#1a1aaf9a57afe4dcf86e87d8dc85e80a41d68644" + integrity sha512-NGxpV25U2W/KKk6M5V2OXuLgrKY+w05ABi66ZEYuCTi7ux1Qv0z+jm7bkgzk1pGGiTVLG+90OGr2nrhbFr5Y4w== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index 69e31279111..a4da8d78445 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { isMacintosh } from 'vs/base/common/platform'; import 'vs/css!./aria'; // Use a max length since we are inserting the whole msg in the DOM and that can cause browsers to freeze for long messages #94233 @@ -69,16 +68,12 @@ export function status(msg: string): void { return; } - if (isMacintosh) { - alert(msg); // VoiceOver does not seem to support status role + if (statusContainer.textContent !== msg) { + dom.clearNode(statusContainer2); + insertMessage(statusContainer, msg); } else { - if (statusContainer.textContent !== msg) { - dom.clearNode(statusContainer2); - insertMessage(statusContainer, msg); - } else { - dom.clearNode(statusContainer); - insertMessage(statusContainer2, msg); - } + dom.clearNode(statusContainer); + insertMessage(statusContainer2, msg); } } diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index a5a79e7fc2d..14a189bd0fc 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -25,25 +25,6 @@ text-decoration: none !important; } -.monaco-button { - color: var(--vscode-button-foreground); - background-color: var(--vscode-button-background); -} - -.monaco-button:hover { - background-color: var(--vscode-button-hoverBackground); -} - -.monaco-button.secondary { - color: var(--vscode-button-secondaryForeground); - background-color: var(--vscode-button-secondaryBackground); -} - -.monaco-button.secondary:hover { - background-color: var(--vscode-button-secondaryHoverBackground); -} - - .monaco-button.disabled:focus, .monaco-button.disabled { opacity: 0.4 !important; @@ -109,19 +90,11 @@ .monaco-button-dropdown .monaco-button-dropdown-separator { padding: 4px 0; cursor: default; - background-color: var(--vscode-button-background); - border-top: 1px solid var(--vscode-button-border); - border-bottom: 1px solid var(--vscode-button-border); -} - -.monaco-button.secondary + .monaco-button-dropdown-separator { - background-color: var(--vscode-button-secondaryBackground); } .monaco-button-dropdown .monaco-button-dropdown-separator > div { height: 100%; width: 1px; - background-color: var(--vscode-button-separator); } .monaco-button-dropdown > .monaco-button.monaco-dropdown-button { @@ -159,3 +132,41 @@ margin: 0 0.2em; color: inherit !important; } + +/* default color styles - based on CSS variables */ + +.monaco-button.default-colors, +.monaco-button-dropdown.default-colors > .monaco-button{ + color: var(--vscode-button-foreground); + background-color: var(--vscode-button-background); +} + +.monaco-button.default-colors:hover, +.monaco-button-dropdown.default-colors > .monaco-button:hover { + background-color: var(--vscode-button-hoverBackground); +} + +.monaco-button.default-colors.secondary, +.monaco-button-dropdown.default-colors > .monaco-button.secondary { + color: var(--vscode-button-secondaryForeground); + background-color: var(--vscode-button-secondaryBackground); +} + +.monaco-button.default-colors.secondary:hover, +.monaco-button-dropdown.default-colors > .monaco-button.secondary:hover { + background-color: var(--vscode-button-secondaryHoverBackground); +} + +.monaco-button-dropdown.default-colors .monaco-button-dropdown-separator { + background-color: var(--vscode-button-background); + border-top: 1px solid var(--vscode-button-border); + border-bottom: 1px solid var(--vscode-button-border); +} + +.monaco-button-dropdown.default-colors .monaco-button.secondary + .monaco-button-dropdown-separator { + background-color: var(--vscode-button-secondaryBackground); +} + +.monaco-button-dropdown.default-colors .monaco-button-dropdown-separator > div { + background-color: var(--vscode-button-separator); +} diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 87d4ed4d9fa..5cf11ce7854 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -373,6 +373,9 @@ export abstract class AbstractScrollableElement extends Widget { } private _onMouseWheel(e: StandardWheelEvent): void { + if (e.browserEvent?.defaultPrevented) { + return; + } const classifier = MouseWheelClassifier.INSTANCE; if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 24547947c56..ef9e64c0aaf 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -755,7 +755,7 @@ export class EventProfiling { } start(listenerCount: number): void { - this._stopWatch = new StopWatch(true); + this._stopWatch = new StopWatch(); this.listenerCount = listenerCount; } diff --git a/src/vs/base/common/stopwatch.ts b/src/vs/base/common/stopwatch.ts index f38627afedb..e32c0dd9d91 100644 --- a/src/vs/base/common/stopwatch.ts +++ b/src/vs/base/common/stopwatch.ts @@ -3,22 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals } from 'vs/base/common/platform'; +// fake definition so that the valid layers check won't trip on this +declare const globalThis: { performance?: { now(): number } }; -const hasPerformanceNow = (globals.performance && typeof globals.performance.now === 'function'); +const hasPerformanceNow = (globalThis.performance && typeof globalThis.performance.now === 'function'); export class StopWatch { - private _highResolution: boolean; private _startTime: number; private _stopTime: number; - public static create(highResolution: boolean = true): StopWatch { + private readonly _now: () => number; + + public static create(highResolution?: boolean): StopWatch { return new StopWatch(highResolution); } - constructor(highResolution: boolean) { - this._highResolution = hasPerformanceNow && highResolution; + constructor(highResolution?: boolean) { + this._now = hasPerformanceNow && highResolution === false ? Date.now : globalThis.performance!.now.bind(globalThis.performance); this._startTime = this._now(); this._stopTime = -1; } @@ -38,8 +40,4 @@ export class StopWatch { } return this._now() - this._startTime; } - - private _now(): number { - return this._highResolution ? globals.performance.now() : Date.now(); - } } diff --git a/src/vs/base/node/osReleaseInfo.ts b/src/vs/base/node/osReleaseInfo.ts new file mode 100644 index 00000000000..f72b0fe82ba --- /dev/null +++ b/src/vs/base/node/osReleaseInfo.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { constants as FSConstants } from 'fs'; +import { open, FileHandle } from 'fs/promises'; +import { createInterface as readLines } from 'readline'; +import * as Platform from 'vs/base/common/platform'; + +type ReleaseInfo = { + id: string; + id_like?: string; + version_id?: string; +}; + +export async function getOSReleaseInfo(errorLogger: (error: any) => void): Promise { + if (Platform.isMacintosh || Platform.isWindows) { + return; + } + + // Extract release information on linux based systems + // using the identifiers specified in + // https://www.freedesktop.org/software/systemd/man/os-release.html + let handle: FileHandle | undefined; + for (const filePath of ['/etc/os-release', '/usr/lib/os-release', '/etc/lsb-release']) { + try { + handle = await open(filePath, FSConstants.R_OK); + break; + } catch (err) { } + } + + if (!handle) { + errorLogger('Unable to retrieve release information from known identifier paths.'); + return; + } + + try { + const osReleaseKeys = new Set([ + 'ID', + 'DISTRIB_ID', + 'ID_LIKE', + 'VERSION_ID', + 'DISTRIB_RELEASE', + ]); + const releaseInfo: ReleaseInfo = { + id: 'unknown' + }; + + for await (const line of readLines({ input: handle.createReadStream(), crlfDelay: Infinity })) { + if (!line.includes('=')) { + continue; + } + const key = line.split('=')[0].toUpperCase().trim(); + if (osReleaseKeys.has(key)) { + const value = line.split('=')[1].replace(/"/g, '').toLowerCase().trim(); + if (key === 'ID' || key === 'DISTRIB_ID') { + releaseInfo.id = value; + } else if (key === 'ID_LIKE') { + releaseInfo.id_like = value; + } else if (key === 'VERSION_ID' || key === 'DISTRIB_RELEASE') { + releaseInfo.version_id = value; + } + } + } + + return releaseInfo; + } catch (err) { + errorLogger(err); + } + + return; +} diff --git a/src/vs/base/node/unc.js b/src/vs/base/node/unc.js index 4e774d1114a..b0af4d38b68 100644 --- a/src/vs/base/node/unc.js +++ b/src/vs/base/node/unc.js @@ -114,7 +114,7 @@ return; } - process.enableUNCAccessChecks = false; + process.restrictUNCAccess = false; } function isUNCAccessRestrictionsDisabled() { @@ -122,7 +122,7 @@ return true; } - return process.enableUNCAccessChecks === false; + return process.restrictUNCAccess === false; } return { diff --git a/src/vs/base/test/common/skipList.test.ts b/src/vs/base/test/common/skipList.test.ts index 9712f498f20..d9c75702f1c 100644 --- a/src/vs/base/test/common/skipList.test.ts +++ b/src/vs/base/test/common/skipList.test.ts @@ -154,18 +154,18 @@ suite('SkipList', function () { // init const list = new SkipList(cmp, max); - let sw = new StopWatch(true); + let sw = new StopWatch(); values.forEach(value => list.set(value, true)); sw.stop(); console.log(`[LIST] ${list.size} elements after ${sw.elapsed()}ms`); let array: number[] = []; - sw = new StopWatch(true); + sw = new StopWatch(); values.forEach(value => array = insertArraySorted(array, value)); sw.stop(); console.log(`[ARRAY] ${array.length} elements after ${sw.elapsed()}ms`); // get - sw = new StopWatch(true); + sw = new StopWatch(); const someValues = [...values].slice(0, values.size / 4); someValues.forEach(key => { const value = list.get(key); // find @@ -174,7 +174,7 @@ suite('SkipList', function () { }); sw.stop(); console.log(`[LIST] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`); - sw = new StopWatch(true); + sw = new StopWatch(); someValues.forEach(key => { const idx = binarySearch(array, key, cmp); // find console.assert(idx >= 0, '[ARRAY] must have ' + key); @@ -185,13 +185,13 @@ suite('SkipList', function () { // insert - sw = new StopWatch(true); + sw = new StopWatch(); someValues.forEach(key => { list.set(-key, false); }); sw.stop(); console.log(`[LIST] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`); - sw = new StopWatch(true); + sw = new StopWatch(); someValues.forEach(key => { array = insertArraySorted(array, -key); }); @@ -199,14 +199,14 @@ suite('SkipList', function () { console.log(`[ARRAY] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`); // delete - sw = new StopWatch(true); + sw = new StopWatch(); someValues.forEach(key => { list.delete(key); // find list.delete(-key); // miss }); sw.stop(); console.log(`[LIST] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`); - sw = new StopWatch(true); + sw = new StopWatch(); someValues.forEach(key => { array = delArraySorted(array, key); // find array = delArraySorted(array, -key); // miss diff --git a/src/vs/base/test/common/ternarySearchtree.test.ts b/src/vs/base/test/common/ternarySearchtree.test.ts index 4ad5d8bbebd..c2f27122730 100644 --- a/src/vs/base/test/common/ternarySearchtree.test.ts +++ b/src/vs/base/test/common/ternarySearchtree.test.ts @@ -961,7 +961,7 @@ suite.skip('TST, perf', function () { function perfTest(name: string, callback: Function) { test(name, function () { if (_profile) { console.profile(name); } - const sw = new StopWatch(true); + const sw = new StopWatch(); callback(); console.log(name, sw.elapsed()); if (_profile) { console.profileEnd(); } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index c85f63877e5..3f3cdef028d 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -12,7 +12,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer, PositionAffinity } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer, PositionAffinity, GlyphMarginLane } from 'vs/editor/common/model'; import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager'; @@ -251,6 +251,43 @@ export interface IOverlayWidget { getPosition(): IOverlayWidgetPosition | null; } +/** + * A glyph margin widget renders in the editor glyph margin. + */ +export interface IGlyphMarginWidget { + /** + * Get a unique identifier of the glyph widget. + */ + getId(): string; + /** + * Get the dom node of the glyph widget. + */ + getDomNode(): HTMLElement; + /** + * Get the placement of the glyph widget. + */ + getPosition(): IGlyphMarginWidgetPosition; +} + +/** + * A position for rendering glyph margin widgets. + */ +export interface IGlyphMarginWidgetPosition { + /** + * The glyph margin lane where the widget should be shown. + */ + lane: GlyphMarginLane; + /** + * The priority order of the widget, used for determining which widget + * to render when there are multiple. + */ + zIndex: number; + /** + * The editor range that this widget applies to. + */ + range: IRange; +} + /** * Type of hit element with the mouse in the editor. */ @@ -993,6 +1030,20 @@ export interface ICodeEditor extends editorCommon.IEditor { */ removeOverlayWidget(widget: IOverlayWidget): void; + /** + * Add a glyph margin widget. Widgets must have unique ids, otherwise they will be overwritten. + */ + addGlyphMarginWidget(widget: IGlyphMarginWidget): void; + /** + * Layout/Reposition a glyph margin widget. This is a ping to the editor to call widget.getPosition() + * and update appropriately. + */ + layoutGlyphMarginWidget(widget: IGlyphMarginWidget): void; + /** + * Remove a glyph margin widget. + */ + removeGlyphMarginWidget(widget: IGlyphMarginWidget): void; + /** * Change the view zones. View zones are lost when a new model is attached to the editor. */ diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 62db35d5a0c..cc0f8669a03 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -786,7 +786,7 @@ class DecorationCSSRules { } /** - * Build the CSS for decorations styled via `glpyhMarginClassName`. + * Build the CSS for decorations styled via `glyphMarginClassName`. */ private getCSSTextForModelDecorationGlyphMarginClassName(opts: IThemeDecorationRenderOptions | undefined): string { if (!opts) { diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index ba5739df58e..a4ffe3053b5 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -143,7 +143,7 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ if (!canSyncModel(this._modelService, resource)) { return Promise.resolve(edits); // File too large } - const sw = StopWatch.create(true); + const sw = StopWatch.create(); const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, pretty)); result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed())); return Promise.race([result, timeout(1000).then(() => edits)]); @@ -158,7 +158,7 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ if (!canSyncModel(this._modelService, resource)) { return Promise.resolve(edits); // File too large } - const sw = StopWatch.create(true); + const sw = StopWatch.create(); const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits, { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, })).catch((err) => { onUnexpectedError(err); diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 922e0dd6e52..82d1fb7a269 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -5,13 +5,14 @@ import * as dom from 'vs/base/browser/dom'; import { Selection } from 'vs/editor/common/core/selection'; +import { Range } from 'vs/editor/common/core/range'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import { IVisibleRangeProvider, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; -import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; +import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays'; @@ -20,7 +21,6 @@ import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/c import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight'; import { DecorationsOverlay } from 'vs/editor/browser/viewParts/decorations/decorations'; import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar'; -import { GlyphMarginOverlay } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; import { IndentGuidesOverlay } from 'vs/editor/browser/viewParts/indentGuides/indentGuides'; import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers'; import { ViewLines } from 'vs/editor/browser/viewParts/lines/viewLines'; @@ -52,6 +52,8 @@ import { BlockDecorations } from 'vs/editor/browser/viewParts/blockDecorations/b import { inputLatency } from 'vs/base/browser/performance'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { WhitespaceOverlay } from 'vs/editor/browser/viewParts/whitespace/whitespace'; +import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; +import { GlyphMarginLane } from 'vs/editor/common/model'; export interface IContentWidgetData { @@ -64,6 +66,11 @@ export interface IOverlayWidgetData { position: IOverlayWidgetPosition | null; } +export interface IGlyphMarginWidgetData { + widget: IGlyphMarginWidget; + position: IGlyphMarginWidgetPosition; +} + export class View extends ViewEventHandler { private readonly _scrollbar: EditorScrollbar; @@ -77,6 +84,7 @@ export class View extends ViewEventHandler { private readonly _viewZones: ViewZones; private readonly _contentWidgets: ViewContentWidgets; private readonly _overlayWidgets: ViewOverlayWidgets; + private readonly _glyphMarginWidgets: GlyphMarginWidgets; private readonly _viewCursors: ViewCursors; private readonly _viewParts: ViewPart[]; @@ -89,6 +97,7 @@ export class View extends ViewEventHandler { private readonly _overflowGuardContainer: FastDomNode; // Actual mutable state + private _shouldRecomputeGlyphMarginLanes: boolean = false; private _renderAnimationFrame: IDisposable | null; constructor( @@ -160,14 +169,18 @@ export class View extends ViewEventHandler { const marginViewOverlays = new MarginViewOverlays(this._context); this._viewParts.push(marginViewOverlays); marginViewOverlays.addDynamicOverlay(new CurrentLineMarginHighlightOverlay(this._context)); - marginViewOverlays.addDynamicOverlay(new GlyphMarginOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new MarginViewLineDecorationsOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new LinesDecorationsOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context)); + // Glyph margin widgets + this._glyphMarginWidgets = new GlyphMarginWidgets(this._context); + this._viewParts.push(this._glyphMarginWidgets); + const margin = new Margin(this._context); margin.getDomNode().appendChild(this._viewZones.marginDomNode); margin.getDomNode().appendChild(marginViewOverlays.getDomNode()); + margin.getDomNode().appendChild(this._glyphMarginWidgets.domNode); this._viewParts.push(margin); // Content widgets @@ -226,10 +239,70 @@ export class View extends ViewEventHandler { } private _flushAccumulatedAndRenderNow(): void { + if (this._shouldRecomputeGlyphMarginLanes) { + this._shouldRecomputeGlyphMarginLanes = false; + this._context.configuration.setGlyphMarginDecorationLaneCount(this._computeGlyphMarginLaneCount()); + } inputLatency.onRenderStart(); this._renderNow(); } + private _computeGlyphMarginLaneCount(): number { + const model = this._context.viewModel.model; + type Glyph = { range: Range; lane: GlyphMarginLane }; + let glyphs: Glyph[] = []; + + // Add all margin decorations + glyphs = glyphs.concat(model.getAllMarginDecorations().map((decoration) => { + const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Left; + return { range: decoration.range, lane }; + })); + + // Add all glyph margin widgets + glyphs = glyphs.concat(this._glyphMarginWidgets.getWidgets().map((widget) => { + const range = model.validateRange(widget.preference.range); + return { range, lane: widget.preference.lane }; + })); + + // Sorted by their start position + glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + + let leftDecRange: Range | null = null; + let rightDecRange: Range | null = null; + for (const decoration of glyphs) { + + if (decoration.lane === GlyphMarginLane.Left && (!leftDecRange || Range.compareRangesUsingEnds(leftDecRange, decoration.range) < 0)) { + // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane + leftDecRange = decoration.range; + } + + if (decoration.lane === GlyphMarginLane.Right && (!rightDecRange || Range.compareRangesUsingEnds(rightDecRange, decoration.range) < 0)) { + // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane + rightDecRange = decoration.range; + } + + if (leftDecRange && rightDecRange) { + + if (leftDecRange.endLineNumber < rightDecRange.startLineNumber) { + // there's no chance for `leftDecRange` to ever intersect something going further + leftDecRange = null; + continue; + } + + if (rightDecRange.endLineNumber < leftDecRange.startLineNumber) { + // there's no chance for `rightDecRange` to ever intersect something going further + rightDecRange = null; + continue; + } + + // leftDecRange and rightDecRange are intersecting or touching => we need two lanes + return 2; + } + } + + return 1; + } + private _createPointerHandlerHelper(): IPointerHandlerHelper { return { viewDomNode: this.domNode.domNode, @@ -317,6 +390,12 @@ export class View extends ViewEventHandler { this._selections = e.selections; return false; } + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + if (e.affectsGlyphMargin) { + this._shouldRecomputeGlyphMarginLanes = true; + } + return false; + } public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { this.domNode.setClassName(this._getEditorClassName()); return false; @@ -548,6 +627,27 @@ export class View extends ViewEventHandler { this._scheduleRender(); } + public addGlyphMarginWidget(widgetData: IGlyphMarginWidgetData): void { + this._glyphMarginWidgets.addWidget(widgetData.widget); + this._shouldRecomputeGlyphMarginLanes = true; + this._scheduleRender(); + } + + public layoutGlyphMarginWidget(widgetData: IGlyphMarginWidgetData): void { + const newPreference = widgetData.position; + const shouldRender = this._glyphMarginWidgets.setWidgetPosition(widgetData.widget, newPreference); + if (shouldRender) { + this._shouldRecomputeGlyphMarginLanes = true; + this._scheduleRender(); + } + } + + public removeGlyphMarginWidget(widgetData: IGlyphMarginWidgetData): void { + this._glyphMarginWidgets.removeWidget(widgetData.widget); + this._shouldRecomputeGlyphMarginLanes = true; + this._scheduleRender(); + } + // --- END CodeEditor helpers } diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 0b35f4b06ed..919d0aad1f9 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -3,14 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; +import { ArrayQueue } from 'vs/base/common/arrays'; import 'vs/css!./glyphMargin'; +import { IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; -import { RenderingContext } from 'vs/editor/browser/view/renderingContext'; -import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; -import * as viewEvents from 'vs/editor/common/viewEvents'; +import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; +import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import * as viewEvents from 'vs/editor/common/viewEvents'; +import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; - +/** + * Represents a decoration that should be shown along the lines from `startLineNumber` to `endLineNumber`. + * This can end up producing multiple `LineDecorationToRender`. + */ export class DecorationToRender { _decorationToRenderBrand: void = undefined; @@ -18,66 +26,59 @@ export class DecorationToRender { public endLineNumber: number; public className: string; public readonly zIndex: number; - public readonly decorationLane: number; - constructor(startLineNumber: number, endLineNumber: number, className: string, zIndex?: number, decorationLane?: number) { + constructor(startLineNumber: number, endLineNumber: number, className: string, zIndex: number | undefined) { this.startLineNumber = +startLineNumber; this.endLineNumber = +endLineNumber; this.className = String(className); this.zIndex = zIndex ?? 0; - this.decorationLane = decorationLane ?? 1; } } -export class RenderedDecoration { +/** + * A decoration that should be shown along a line. + */ +export class LineDecorationToRender { constructor( public readonly className: string, public readonly zIndex: number, ) { } } -export class LineRenderedDecorations { +/** + * Decorations to render on a visible line. + */ +export class VisibleLineDecorationsToRender { - private readonly lanes: RenderedDecoration[][] = []; + private readonly decorations: LineDecorationToRender[] = []; - public add(lane: number, decoration: RenderedDecoration) { - while (lane >= this.lanes.length) { - this.lanes.push([]); - } - this.lanes[lane].push(decoration); + public add(decoration: LineDecorationToRender) { + this.decorations.push(decoration); } - public getLaneDecorations(laneIndex: number): RenderedDecoration[] { - if (laneIndex < this.lanes.length) { - return this.lanes[laneIndex]; - } - return []; - } - - public isEmpty(): boolean { - for (const lane of this.lanes) { - if (lane.length > 0) { - return false; - } - } - return true; + public getDecorations(): LineDecorationToRender[] { + return this.decorations; } } export abstract class DedupOverlay extends DynamicViewOverlay { - protected _render(visibleStartLineNumber: number, visibleEndLineNumber: number, decorations: DecorationToRender[], decorationLaneCount: number): LineRenderedDecorations[] { + /** + * Returns an array with an element for each visible line number. + */ + protected _render(visibleStartLineNumber: number, visibleEndLineNumber: number, decorations: DecorationToRender[]): VisibleLineDecorationsToRender[] { - const output: LineRenderedDecorations[] = []; + const output: VisibleLineDecorationsToRender[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const lineIndex = lineNumber - visibleStartLineNumber; - output[lineIndex] = new LineRenderedDecorations(); + output[lineIndex] = new VisibleLineDecorationsToRender(); } if (decorations.length === 0) { return output; } + // Sort decorations by className, then by startLineNumber and then by endLineNumber decorations.sort((a, b) => { if (a.className === b.className) { if (a.startLineNumber === b.startLineNumber) { @@ -96,9 +97,9 @@ export abstract class DedupOverlay extends DynamicViewOverlay { const zIndex = d.zIndex; let startLineIndex = Math.max(d.startLineNumber, visibleStartLineNumber) - visibleStartLineNumber; const endLineIndex = Math.min(d.endLineNumber, visibleEndLineNumber) - visibleStartLineNumber; - const lane = Math.min(d.decorationLane, decorationLaneCount); if (prevClassName === className) { + // Here we avoid rendering the same className multiple times on the same line startLineIndex = Math.max(prevEndLineIndex + 1, startLineIndex); prevEndLineIndex = Math.max(prevEndLineIndex, endLineIndex); } else { @@ -107,7 +108,7 @@ export abstract class DedupOverlay extends DynamicViewOverlay { } for (let i = startLineIndex; i <= prevEndLineIndex; i++) { - output[i].add(lane, new RenderedDecoration(className, zIndex)); + output[i].add(new LineDecorationToRender(className, zIndex)); } } @@ -115,40 +116,54 @@ export abstract class DedupOverlay extends DynamicViewOverlay { } } -export class GlyphMarginOverlay extends DedupOverlay { +export class GlyphMarginWidgets extends ViewPart { + + public domNode: FastDomNode; - private readonly _context: ViewContext; private _lineHeight: number; private _glyphMargin: boolean; private _glyphMarginLeft: number; private _glyphMarginWidth: number; private _glyphMarginDecorationLaneCount: number; - private _renderResult: string[] | null; + + private _managedDomNodes: FastDomNode[]; + private _decorationGlyphsToRender: DecorationBasedGlyph[]; + + private _widgets: { [key: string]: IWidgetData } = {}; constructor(context: ViewContext) { - super(); + super(context); this._context = context; const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); + this.domNode = createFastDomNode(document.createElement('div')); + this.domNode.setClassName('glyphMarginWidgets'); + this.domNode.setPosition('absolute'); + this.domNode.setTop(0); + this._lineHeight = options.get(EditorOption.lineHeight); this._glyphMargin = options.get(EditorOption.glyphMargin); this._glyphMarginLeft = layoutInfo.glyphMarginLeft; this._glyphMarginWidth = layoutInfo.glyphMarginWidth; this._glyphMarginDecorationLaneCount = layoutInfo.glyphMarginDecorationLaneCount; - this._renderResult = null; - this._context.addEventHandler(this); + this._managedDomNodes = []; + this._decorationGlyphsToRender = []; } public override dispose(): void { - this._context.removeEventHandler(this); - this._renderResult = null; + this._managedDomNodes = []; + this._decorationGlyphsToRender = []; + this._widgets = {}; super.dispose(); } - // --- begin event handlers + public getWidgets(): IWidgetData[] { + return Object.values(this._widgets); + } + // --- begin event handlers public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -184,85 +199,302 @@ export class GlyphMarginOverlay extends DedupOverlay { // --- end event handlers - protected _getDecorations(ctx: RenderingContext): DecorationToRender[] { - const decorations = ctx.getDecorationsInViewport(); - const r: DecorationToRender[] = []; - let rLen = 0; - for (let i = 0, len = decorations.length; i < len; i++) { - const d = decorations[i]; - const glyphMarginClassName = d.options.glyphMarginClassName; - const zIndex = d.options.zIndex; - const lane = d.options.glyphMargin?.position; - if (glyphMarginClassName) { - r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName, zIndex, lane); - } - } - return r; + // --- begin widget management + + public addWidget(widget: IGlyphMarginWidget): void { + const domNode = createFastDomNode(widget.getDomNode()); + + this._widgets[widget.getId()] = { + widget: widget, + preference: widget.getPosition(), + domNode: domNode, + renderInfo: null + }; + + domNode.setPosition('absolute'); + domNode.setDisplay('none'); + domNode.setAttribute('widgetId', widget.getId()); + this.domNode.appendChild(domNode); + + this.setShouldRender(); } + public setWidgetPosition(widget: IGlyphMarginWidget, preference: IGlyphMarginWidgetPosition): boolean { + const myWidget = this._widgets[widget.getId()]; + if (myWidget.preference.lane === preference.lane + && myWidget.preference.zIndex === preference.zIndex + && Range.equalsRange(myWidget.preference.range, preference.range)) { + return false; + } + + myWidget.preference = preference; + this.setShouldRender(); + + return true; + } + + public removeWidget(widget: IGlyphMarginWidget): void { + const widgetId = widget.getId(); + if (this._widgets[widgetId]) { + const widgetData = this._widgets[widgetId]; + const domNode = widgetData.domNode.domNode; + delete this._widgets[widgetId]; + + domNode.parentNode?.removeChild(domNode); + this.setShouldRender(); + } + } + + // --- end widget management + + private _collectDecorationBasedGlyphRenderRequest(ctx: RenderingContext, requests: GlyphRenderRequest[]): void { + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const decorations = ctx.getDecorationsInViewport(); + + for (const d of decorations) { + const glyphMarginClassName = d.options.glyphMarginClassName; + if (!glyphMarginClassName) { + continue; + } + + const startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber); + const endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber); + const lane = Math.min(d.options.glyphMargin?.position ?? 1, this._glyphMarginDecorationLaneCount); + const zIndex = d.options.zIndex ?? 0; + + for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { + requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, lane, zIndex, glyphMarginClassName)); + } + } + } + + private _collectWidgetBasedGlyphRenderRequest(ctx: RenderingContext, requests: GlyphRenderRequest[]): void { + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + + for (const widget of Object.values(this._widgets)) { + const range = widget.preference.range; + if (range.endLineNumber < visibleStartLineNumber || range.startLineNumber > visibleEndLineNumber) { + // The widget is not in the viewport + continue; + } + + // The widget is in the viewport, find a good line for it + const widgetLineNumber = Math.max(range.startLineNumber, visibleStartLineNumber); + const lane = Math.min(widget.preference.lane, this._glyphMarginDecorationLaneCount); + requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, lane, widget.preference.zIndex, widget)); + } + } + + private _collectSortedGlyphRenderRequests(ctx: RenderingContext): GlyphRenderRequest[] { + + const requests: GlyphRenderRequest[] = []; + + this._collectDecorationBasedGlyphRenderRequest(ctx, requests); + this._collectWidgetBasedGlyphRenderRequest(ctx, requests); + + // sort requests by lineNumber ASC, lane ASC, zIndex DESC, type DESC (widgets first), className ASC + // don't change this sort unless you understand `prepareRender` below. + requests.sort((a, b) => { + if (a.lineNumber === b.lineNumber) { + if (a.lane === b.lane) { + if (a.zIndex === b.zIndex) { + if (b.type === a.type) { + if (a.type === GlyphRenderRequestType.Decoration && b.type === GlyphRenderRequestType.Decoration) { + return (a.className < b.className ? -1 : 1); + } + return 0; + } + return b.type - a.type; + } + return b.zIndex - a.zIndex; + } + return a.lane - b.lane; + } + return a.lineNumber - b.lineNumber; + }); + + return requests; + } + + /** + * Will store render information in each widget's renderInfo and in `_decorationGlyphsToRender`. + */ public prepareRender(ctx: RenderingContext): void { if (!this._glyphMargin) { - this._renderResult = null; + this._decorationGlyphsToRender = []; return; } - const visibleStartLineNumber = ctx.visibleRange.startLineNumber; - const visibleEndLineNumber = ctx.visibleRange.endLineNumber; - const decorationsToRender = this._getDecorations(ctx); - const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, decorationsToRender, this._glyphMarginDecorationLaneCount); + for (const widget of Object.values(this._widgets)) { + widget.renderInfo = null; + } - const lineHeight = this._lineHeight.toString(); - const width = (Math.round(this._glyphMarginWidth / this._glyphMarginDecorationLaneCount)).toString(); - const common = '" style="width:' + width + 'px' + ';height:' + lineHeight + 'px;'; + const requests = new ArrayQueue(this._collectSortedGlyphRenderRequests(ctx)); + const decorationGlyphsToRender: DecorationBasedGlyph[] = []; + while (requests.length > 0) { + const first = requests.peek(); + if (!first) { + // not possible + break; + } - const output: string[] = []; - for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - const lineIndex = lineNumber - visibleStartLineNumber; - const renderInfo = toRender[lineIndex]; + // Requests are sorted by lineNumber and lane, so we read all requests for this particular location + const requestsAtLocation = requests.takeWhile((el) => el.lineNumber === first.lineNumber && el.lane === first.lane); + if (!requestsAtLocation || requestsAtLocation.length === 0) { + // not possible + break; + } - if (renderInfo.isEmpty()) { - output[lineIndex] = ''; - } else { - let css = ''; - for (let lane = 1; lane <= this._glyphMarginDecorationLaneCount; lane += 1) { - const decorations = renderInfo.getLaneDecorations(lane); - if (decorations.length === 0) { - continue; + const winner = requestsAtLocation[0]; + if (winner.type === GlyphRenderRequestType.Decoration) { + // combine all decorations with the same z-index + + const classNames: string[] = []; + // requests are sorted by zIndex, type, and className so we can dedup className by looking at the previous one + for (const request of requestsAtLocation) { + if (request.zIndex !== winner.zIndex || request.type !== winner.type) { + break; } - decorations.sort((a, b) => b.zIndex - a.zIndex); - // Render winning decorations with the same zIndex together - const winningDecoration: RenderedDecoration = decorations[0]; - const winningDecorationClassNames = [winningDecoration.className]; - for (let i = 1; i < decorations.length; i += 1) { - const decoration = decorations[i]; - if (decoration.zIndex !== winningDecoration.zIndex) { - break; - } - winningDecorationClassNames.push(decoration.className); + if (classNames.length === 0 || classNames[classNames.length - 1] !== request.className) { + classNames.push(request.className); } - const left = (this._glyphMarginLeft + (lane - 1) * this._lineHeight).toString(); - css += ( - '
' - ); } - output[lineIndex] = css; + + decorationGlyphsToRender.push(winner.accept(classNames.join(' '))); // TODO@joyceerhl Implement overflow for remaining decorations + } else { + // widgets cannot be combined + winner.widget.renderInfo = { + lineNumber: winner.lineNumber, + lane: winner.lane, + }; + } + } + this._decorationGlyphsToRender = decorationGlyphsToRender; + } + + public render(ctx: RestrictedRenderingContext): void { + if (!this._glyphMargin) { + for (const widget of Object.values(this._widgets)) { + widget.domNode.setDisplay('none'); + } + while (this._managedDomNodes.length > 0) { + const domNode = this._managedDomNodes.pop(); + domNode?.domNode.remove(); + } + return; + } + + const width = (Math.round(this._glyphMarginWidth / this._glyphMarginDecorationLaneCount)); + + // Render widgets + for (const widget of Object.values(this._widgets)) { + if (!widget.renderInfo) { + // this widget is not visible + widget.domNode.setDisplay('none'); + } else { + const top = ctx.viewportData.relativeVerticalOffset[widget.renderInfo.lineNumber - ctx.viewportData.startLineNumber]; + const left = this._glyphMarginLeft + (widget.renderInfo.lane - 1) * this._lineHeight; + + widget.domNode.setDisplay('block'); + widget.domNode.setTop(top); + widget.domNode.setLeft(left); + widget.domNode.setWidth(width); + widget.domNode.setHeight(this._lineHeight); } } - this._renderResult = output; - } + // Render decorations, reusing previous dom nodes as possible + for (let i = 0; i < this._decorationGlyphsToRender.length; i++) { + const dec = this._decorationGlyphsToRender[i]; + const top = ctx.viewportData.relativeVerticalOffset[dec.lineNumber - ctx.viewportData.startLineNumber]; + const left = this._glyphMarginLeft + (dec.lane - 1) * this._lineHeight; - public render(startLineNumber: number, lineNumber: number): string { - if (!this._renderResult) { - return ''; + let domNode: FastDomNode; + if (i < this._managedDomNodes.length) { + domNode = this._managedDomNodes[i]; + } else { + domNode = createFastDomNode(document.createElement('div')); + this._managedDomNodes.push(domNode); + this.domNode.appendChild(domNode); + } + + domNode.setClassName(`cgmr codicon ` + dec.combinedClassName); + domNode.setPosition(`absolute`); + domNode.setTop(top); + domNode.setLeft(left); + domNode.setWidth(width); + domNode.setHeight(this._lineHeight); } - const lineIndex = lineNumber - startLineNumber; - if (lineIndex < 0 || lineIndex >= this._renderResult.length) { - return ''; + + // remove extra dom nodes + while (this._managedDomNodes.length > this._decorationGlyphsToRender.length) { + const domNode = this._managedDomNodes.pop(); + domNode?.domNode.remove(); } - return this._renderResult[lineIndex]; } } + +export interface IWidgetData { + widget: IGlyphMarginWidget; + preference: IGlyphMarginWidgetPosition; + domNode: FastDomNode; + /** + * it will contain the location where to render the widget + * or null if the widget is not visible + */ + renderInfo: IRenderInfo | null; +} + +export interface IRenderInfo { + lineNumber: number; + lane: number; +} + +const enum GlyphRenderRequestType { + Decoration = 0, + Widget = 1 +} + +/** + * A request to render a decoration in the glyph margin at a certain location. + */ +class DecorationBasedGlyphRenderRequest { + public readonly type = GlyphRenderRequestType.Decoration; + + constructor( + public readonly lineNumber: number, + public readonly lane: number, + public readonly zIndex: number, + public readonly className: string, + ) { } + + accept(combinedClassName: string): DecorationBasedGlyph { + return new DecorationBasedGlyph(this.lineNumber, this.lane, combinedClassName); + } +} + +/** + * A request to render a widget in the glyph margin at a certain location. + */ +class WidgetBasedGlyphRenderRequest { + public readonly type = GlyphRenderRequestType.Widget; + + constructor( + public readonly lineNumber: number, + public readonly lane: number, + public readonly zIndex: number, + public readonly widget: IWidgetData, + ) { } +} + +type GlyphRenderRequest = DecorationBasedGlyphRenderRequest | WidgetBasedGlyphRenderRequest; + +class DecorationBasedGlyph { + constructor( + public readonly lineNumber: number, + public readonly lane: number, + public readonly combinedClassName: string + ) { } +} diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index 9ed72e6acf0..95d8caaed51 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -91,7 +91,7 @@ export class LinesDecorationsOverlay extends DedupOverlay { public prepareRender(ctx: RenderingContext): void { const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; - const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx), 1); + const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx)); const left = this._decorationsLeft.toString(); const width = this._decorationsWidth.toString(); @@ -100,7 +100,7 @@ export class LinesDecorationsOverlay extends DedupOverlay { const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const lineIndex = lineNumber - visibleStartLineNumber; - const decorations = toRender[lineIndex].getLaneDecorations(1); // there is only one lane, see _render call above + const decorations = toRender[lineIndex].getDecorations(); let lineOutput = ''; for (const decoration of decorations) { lineOutput += '
'; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index dedb0237589..d970536c421 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -21,7 +21,7 @@ import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; -import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view'; +import { IContentWidgetData, IGlyphMarginWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view'; import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; @@ -255,6 +255,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private _contentWidgets: { [key: string]: IContentWidgetData }; private _overlayWidgets: { [key: string]: IOverlayWidgetData }; + private _glyphMarginWidgets: { [key: string]: IGlyphMarginWidgetData }; /** * map from "parent" decoration type to live decoration ids. @@ -323,6 +324,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._contentWidgets = {}; this._overlayWidgets = {}; + this._glyphMarginWidgets = {}; let contributions: IEditorContributionDescription[]; if (Array.isArray(codeEditorWidgetOptions.contributions)) { @@ -1510,6 +1512,45 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } } + public addGlyphMarginWidget(widget: editorBrowser.IGlyphMarginWidget): void { + const widgetData: IGlyphMarginWidgetData = { + widget: widget, + position: widget.getPosition() + }; + + if (this._glyphMarginWidgets.hasOwnProperty(widget.getId())) { + console.warn('Overwriting a glyph margin widget with the same id.'); + } + + this._glyphMarginWidgets[widget.getId()] = widgetData; + + if (this._modelData && this._modelData.hasRealView) { + this._modelData.view.addGlyphMarginWidget(widgetData); + } + } + + public layoutGlyphMarginWidget(widget: editorBrowser.IGlyphMarginWidget): void { + const widgetId = widget.getId(); + if (this._glyphMarginWidgets.hasOwnProperty(widgetId)) { + const widgetData = this._glyphMarginWidgets[widgetId]; + widgetData.position = widget.getPosition(); + if (this._modelData && this._modelData.hasRealView) { + this._modelData.view.layoutGlyphMarginWidget(widgetData); + } + } + } + + public removeGlyphMarginWidget(widget: editorBrowser.IGlyphMarginWidget): void { + const widgetId = widget.getId(); + if (this._glyphMarginWidgets.hasOwnProperty(widgetId)) { + const widgetData = this._glyphMarginWidgets[widgetId]; + delete this._glyphMarginWidgets[widgetId]; + if (this._modelData && this._modelData.hasRealView) { + this._modelData.view.removeGlyphMarginWidget(widgetData); + } + } + } + public changeViewZones(callback: (accessor: editorBrowser.IViewZoneChangeAccessor) => void): void { if (!this._modelData || !this._modelData.hasRealView) { return; @@ -1718,6 +1759,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE view.addOverlayWidget(this._overlayWidgets[widgetId]); } + keys = Object.keys(this._glyphMarginWidgets); + for (let i = 0, len = keys.length; i < len; i++) { + const widgetId = keys[i]; + view.addGlyphMarginWidget(this._glyphMarginWidgets[widgetId]); + } + view.render(false, true); view.domNode.domNode.setAttribute('data-uri', model.uri.toString()); } diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts index 6094cee8684..9a7a0a72073 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts @@ -55,9 +55,11 @@ export class DiffModel extends Disposable implements IDiffEditorViewModel { if (!diff) { return; } - if (!this._showMoves.get()) { - const textEdits = TextEditInfo.fromModelContentChanges(e.changes); - this._lastDiff = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified); + + const textEdits = TextEditInfo.fromModelContentChanges(e.changes); + const result = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified); + if (result) { + this._lastDiff = result; this._diff.set(DiffState.fromDiffResult(this._lastDiff), undefined); const currentSyncedMovedText = this.syncedMovedTexts.get(); this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, undefined); @@ -70,13 +72,16 @@ export class DiffModel extends Disposable implements IDiffEditorViewModel { if (!diff) { return; } - if (!this._showMoves.get()) { - const textEdits = TextEditInfo.fromModelContentChanges(e.changes); - this._lastDiff = applyOriginalEdits(this._lastDiff!, textEdits, model.original, model.modified); + + const textEdits = TextEditInfo.fromModelContentChanges(e.changes); + const result = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified); + if (result) { + this._lastDiff = result; this._diff.set(DiffState.fromDiffResult(this._lastDiff), undefined); const currentSyncedMovedText = this.syncedMovedTexts.get(); this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, undefined); } + debouncer.schedule(); })); @@ -107,8 +112,8 @@ export class DiffModel extends Disposable implements IDiffEditorViewModel { computeMoves: this._showMoves.read(reader), }); - result = applyOriginalEdits(result, originalTextEditInfos, model.original, model.modified); - result = applyModifiedEdits(result, modifiedTextEditInfos, model.original, model.modified); + result = applyOriginalEdits(result, originalTextEditInfos, model.original, model.modified) ?? result; + result = applyModifiedEdits(result, modifiedTextEditInfos, model.original, model.modified) ?? result; const newUnchangedRegions = UnchangedRegion.fromDiffs(result.changes, model.original.getLineCount(), model.modified.getLineCount()); @@ -327,13 +332,16 @@ export class UnchangedRegion { } } -function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff { +function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff | undefined { if (textEdits.length === 0) { return diff; } const diff2 = flip(diff); const diff3 = applyModifiedEdits(diff2, textEdits, modifiedTextModel, originalTextModel); + if (!diff3) { + return undefined; + } return flip(diff3); } @@ -346,10 +354,14 @@ function flip(diff: IDocumentDiff): IDocumentDiff { }; } -function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff { +function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff | undefined { if (textEdits.length === 0) { return diff; } + if (diff.changes.some(c => !c.innerChanges) || diff.moves.length > 0) { + // TODO support these cases + return undefined; + } const changes = applyModifiedEditsToLineRangeMappings(diff.changes, textEdits, originalTextModel, modifiedTextModel); diff --git a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts index eddfb9c0ad9..385b3686e9c 100644 --- a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts +++ b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts @@ -67,7 +67,7 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I return c.result; } - const sw = StopWatch.create(true); + const sw = StopWatch.create(); const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options, this.diffAlgorithm); const timeMs = sw.elapsed(); diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 298a16f44b4..c8cddbcc6c9 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -685,7 +685,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { public async textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> { - const sw = new StopWatch(true); + const sw = new StopWatch(); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); const seen = new Set(); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index cf61c0bac20..eb18dced3c1 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -19,7 +19,7 @@ import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorState, IViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { EndOfLinePreference, GlyphMarginLane, IAttachedView, ICursorStateComputer, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from 'vs/editor/common/textModelGuides'; import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/textModelEvents'; @@ -459,54 +459,6 @@ export class ViewModel extends Disposable implements IViewModel { this._register(this.model.onDidChangeDecorations((e) => { this._decorations.onModelDecorationsChanged(); - - // Determine whether we need to resize the glyph margin - if (e.affectsGlyphMargin) { - const decorations = this.model.getAllMarginDecorations(); - - let hasTwoLanes = false; - - // Decorations are already sorted by their start position, but protect against future changes - decorations.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - - let leftDecRange: Range | null = null; - let rightDecRange: Range | null = null; - for (const decoration of decorations) { - const position = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Left; - - if (position === GlyphMarginLane.Left && (!leftDecRange || Range.compareRangesUsingEnds(leftDecRange, decoration.range) < 0)) { - // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane - leftDecRange = decoration.range; - } - - if (position === GlyphMarginLane.Right && (!rightDecRange || Range.compareRangesUsingEnds(rightDecRange, decoration.range) < 0)) { - // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane - rightDecRange = decoration.range; - } - - if (leftDecRange && rightDecRange) { - - if (leftDecRange.endLineNumber < rightDecRange.startLineNumber) { - // there's no chance for `leftDecRange` to ever intersect something going further - leftDecRange = null; - continue; - } - - if (rightDecRange.endLineNumber < leftDecRange.startLineNumber) { - // there's no chance for `rightDecRange` to ever intersect something going further - rightDecRange = null; - continue; - } - - // leftDecRange and rightDecRange are intersecting or touching => we need two lanes - hasTwoLanes = true; - break; - } - } - - this._configuration.setGlyphMarginDecorationLaneCount(hasTwoLanes ? 2 : 1); - } - this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); this._eventDispatcher.emitOutgoingEvent(new ModelDecorationsChangedEvent(e)); })); diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index d87acd50139..8fe86b49ed8 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -311,7 +311,7 @@ export class FoldingController extends Disposable implements IEditorContribution if (!foldingModel) { // null if editor has been disposed, or folding turned off return null; } - const sw = new StopWatch(true); + const sw = new StopWatch(); const provider = this.getRangeProvider(foldingModel.textModel); const foldingRegionPromise = this.foldingRegionPromise = createCancelablePromise(token => provider.compute(token)); return foldingRegionPromise.then(foldingRanges => { @@ -1269,4 +1269,3 @@ CommandsRegistry.registerCommand('_executeFoldingRangeProvider', async function rangeProvider.dispose(); } }); - diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index dd8ae92d96d..8d74bd7bdcd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -70,10 +70,13 @@ export class SingleTextEdit { const suggestionAddedIndentationLength = getLeadingWhitespace(edit.text).length; const replacedIndentation = sourceLine.substring(edit.range.startColumn - 1, sourceIndentationLength); - const rangeThatDoesNotReplaceIndentation = Range.fromPositions( - edit.range.getStartPosition().delta(0, replacedIndentation.length), - edit.range.getEndPosition() - ); + + const [startPosition, endPosition] = [edit.range.getStartPosition(), edit.range.getEndPosition()]; + const newStartPosition = + startPosition.column + replacedIndentation.length <= endPosition.column + ? startPosition.delta(0, replacedIndentation.length) + : endPosition; + const rangeThatDoesNotReplaceIndentation = Range.fromPositions(newStartPosition, endPosition); const suggestionWithoutIndentationChange = edit.text.startsWith(replacedIndentation) diff --git a/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts index 98d97438d14..4336914a3ce 100644 --- a/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts @@ -16,7 +16,7 @@ import { overviewRulerRangeHighlight } from 'vs/editor/common/core/editorColorRe import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; import { IKeyMods, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { alert } from 'vs/base/browser/ui/aria/aria'; +import { status } from 'vs/base/browser/ui/aria/aria'; interface IEditorLineDecoration { readonly rangeHighlightId: string; @@ -149,7 +149,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu } const model = editor.getModel(); if (model && 'getLineContent' in model) { - alert(`${model.getLineContent(options.range.startLineNumber)}`); + status(`${model.getLineContent(options.range.startLineNumber)}`); } } diff --git a/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts index c3c33eca509..b06590e3255 100644 --- a/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts @@ -211,7 +211,7 @@ suite('SmartSelect', () => { async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise { const index = value.indexOf('|'); - value = value.replace('|', ''); + value = value.replace('|', ''); // CodeQL [SM02383] js/incomplete-sanitization this is purpose only the first | character const model = modelService.createModel(value, new StaticLanguageSelector(languageId), URI.parse('fake:lang')); const pos = model.getPositionAt(index); diff --git a/src/vs/editor/contrib/suggest/browser/suggest.ts b/src/vs/editor/contrib/suggest/browser/suggest.ts index 3409fa42392..9e8fbaee192 100644 --- a/src/vs/editor/contrib/suggest/browser/suggest.ts +++ b/src/vs/editor/contrib/suggest/browser/suggest.ts @@ -73,7 +73,7 @@ export class CompletionItem { readonly extensionId?: ExtensionIdentifier; // resolving - private _isResolved?: boolean; + private _resolveDuration?: number; private _resolveCache?: Promise; constructor( @@ -122,32 +122,37 @@ export class CompletionItem { // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); - this._isResolved = true; + this._resolveDuration = 0; } } // ---- resolving get isResolved(): boolean { - return !!this._isResolved; + return this._resolveDuration !== undefined; + } + + get resolveDuration(): number { + return this._resolveDuration !== undefined ? this._resolveDuration : -1; } async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; - this._isResolved = false; + this._resolveDuration = undefined; }); + const sw = new StopWatch(true); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); - this._isResolved = true; + this._resolveDuration = sw.elapsed(); sub.dispose(); }, err => { if (isCancellationError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; - this._isResolved = false; + this._resolveDuration = undefined; } }); } @@ -213,7 +218,7 @@ export async function provideSuggestionItems( token: CancellationToken = CancellationToken.None ): Promise { - const sw = new StopWatch(true); + const sw = new StopWatch(); position = position.clone(); const word = model.getWordAtPosition(position); @@ -275,7 +280,7 @@ export async function provideSuggestionItems( if (options.providerFilter.size > 0 && !options.providerFilter.has(_snippetSuggestSupport)) { return; } - const sw = new StopWatch(true); + const sw = new StopWatch(); const list = await _snippetSuggestSupport.provideCompletionItems(model, position, context, token); onCompletionList(_snippetSuggestSupport, list, sw); })(); @@ -300,7 +305,7 @@ export async function provideSuggestionItems( return; } try { - const sw = new StopWatch(true); + const sw = new StopWatch(); const list = await provider.provideCompletionItems(model, position, context, token); didAddResult = onCompletionList(provider, list, sw) || didAddResult; } catch (err) { diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 0341a938c24..390faf46a08 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -349,7 +349,7 @@ export class SuggestController implements IEditorContribution { } else if (!isResolved) { // async additional edits - const sw = new StopWatch(true); + const sw = new StopWatch(); let position: IPosition | undefined; const docListener = model.onDidChangeContent(e => { @@ -482,20 +482,24 @@ export class SuggestController implements IEditorContribution { // clear only now - after all tasks are done Promise.all(tasks).finally(() => { - this._reportSuggestionAcceptedTelemetry(item, model, event, isResolved); + this._reportSuggestionAcceptedTelemetry(item, model, isResolved); this.model.clear(); cts.dispose(); }); } - private _telemetryGate: number = 0; - private _reportSuggestionAcceptedTelemetry(item: CompletionItem, model: ITextModel, acceptedSuggestion: ISelectedSuggestion, itemResolved: boolean) { - if (this._telemetryGate++ % 100 !== 0) { + private _reportSuggestionAcceptedTelemetry(item: CompletionItem, model: ITextModel, itemResolved: boolean) { + + if (Math.floor(Math.random() * 100) === 0) { + // throttle telemetry event because accepting completions happens a lot return; } - type AcceptedSuggestion = { providerId: string; fileExtension: string; languageId: string; basenameHash: string; kind: number; itemResolved: boolean }; + type AcceptedSuggestion = { + providerId: string; fileExtension: string; languageId: string; basenameHash: string; kind: number; + resolveInfo: number; resolveDuration: number; + }; type AcceptedSuggestionClassification = { owner: 'jrieken'; comment: 'Information accepting completion items'; @@ -504,18 +508,18 @@ export class SuggestController implements IEditorContribution { fileExtension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension of the file into which the completion was inserted' }; languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Language type of the file into which the completion was inserted' }; kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The completion item kind' }; - itemResolved: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'If the item was inserted before resolving was done' }; + resolveInfo: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'If the item was inserted before resolving was done' }; + resolveDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long resolving took to finish' }; }; - // _debugDisplayName looks like `vscode.css-language-features(/-:)`, where the last bit is the trigger chars - // normalize it to just the extension ID and lowercase - const providerId = item.extensionId ? item.extensionId.value : (acceptedSuggestion.item.provider._debugDisplayName ?? 'unknown').split('(', 1)[0].toLowerCase(); + this._telemetryService.publicLog2('suggest.acceptedSuggestion', { - providerId, + providerId: item.extensionId?.value ?? 'unknown', kind: item.completion.kind, basenameHash: hash(basename(model.uri)).toString(16), languageId: model.getLanguageId(), fileExtension: extname(model.uri), - itemResolved + resolveInfo: !item.provider.resolveCompletionItem ? -1 : itemResolved ? 1 : 0, + resolveDuration: item.resolveDuration }); } diff --git a/src/vs/editor/contrib/tokenization/browser/tokenization.ts b/src/vs/editor/contrib/tokenization/browser/tokenization.ts index 8e673149542..800f950ad29 100644 --- a/src/vs/editor/contrib/tokenization/browser/tokenization.ts +++ b/src/vs/editor/contrib/tokenization/browser/tokenization.ts @@ -24,7 +24,7 @@ class ForceRetokenizeAction extends EditorAction { } const model = editor.getModel(); model.tokenization.resetTokenization(); - const sw = new StopWatch(true); + const sw = new StopWatch(); model.tokenization.forceTokenization(model.getLineCount()); sw.stop(); console.log(`tokenization took ${sw.elapsed()}`); diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index ee6b06b6319..a11868bc8cb 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -9,7 +9,7 @@ import { CancelablePromise, createCancelablePromise, first, timeout } from 'vs/b import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorContributionInstantiation, IActionOptions, registerEditorAction, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,6 +29,7 @@ import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { getHighlightDecorationOptions } from 'vs/editor/contrib/wordHighlighter/browser/highlightDecorations'; +import { Iterable } from 'vs/base/common/iterator'; const ctxHasWordHighlights = new RawContextKey('hasWordHighlights', false); @@ -192,9 +193,12 @@ class WordHighlighter { private readonly _hasWordHighlights: IContextKey; private _ignorePositionChangeEvent: boolean; - constructor(editor: IActiveCodeEditor, providers: LanguageFeatureRegistry, contextKeyService: IContextKeyService) { + private readonly linkedHighlighters: () => Iterable; + + constructor(editor: IActiveCodeEditor, providers: LanguageFeatureRegistry, linkedHighlighters: () => Iterable, contextKeyService: IContextKeyService) { this.editor = editor; this.providers = providers; + this.linkedHighlighters = linkedHighlighters; this._hasWordHighlights = ctxHasWordHighlights.bindTo(contextKeyService); this._ignorePositionChangeEvent = false; this.occurrencesHighlight = this.editor.getOption(EditorOption.occurrencesHighlight); @@ -453,6 +457,15 @@ class WordHighlighter { this.decorations.set(decorations); this._hasWordHighlights.set(this.hasDecorations()); + + // update decorators of friends + for (const other of this.linkedHighlighters()) { + if (other?.editor.getModel() === this.editor.getModel()) { + other._stopAll(); + other.decorations.set(decorations); + other._hasWordHighlights.set(other.hasDecorations()); + } + } } public dispose(): void { @@ -470,13 +483,15 @@ export class WordHighlighterContribution extends Disposable implements IEditorCo } private wordHighlighter: WordHighlighter | null; + private linkedContributions: Set; constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService) { super(); this.wordHighlighter = null; + this.linkedContributions = new Set(); const createWordHighlighterIfPossible = () => { if (editor.hasModel()) { - this.wordHighlighter = new WordHighlighter(editor, languageFeaturesService.documentHighlightProvider, contextKeyService); + this.wordHighlighter = new WordHighlighter(editor, languageFeaturesService.documentHighlightProvider, () => Iterable.map(this.linkedContributions, c => c.wordHighlighter), contextKeyService); } }; this._register(editor.onDidChangeModel((e) => { @@ -514,6 +529,19 @@ export class WordHighlighterContribution extends Disposable implements IEditorCo this.wordHighlighter?.stop(); } + public linkWordHighlighters(editor: ICodeEditor): IDisposable { + const other = WordHighlighterContribution.get(editor); + if (!other) { + return Disposable.None; + } + this.linkedContributions.add(other); + other.linkedContributions.add(this); + return toDisposable(() => { + this.linkedContributions.delete(other); + other.linkedContributions.delete(this); + }); + } + public override dispose(): void { if (this.wordHighlighter) { this.wordHighlighter.dispose(); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 63ade6f2143..cc2ede5219c 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -87,7 +87,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { WorkspaceEdit } from 'vs/editor/common/languages'; -import { AudioCue, IAudioCueService, Sound } from 'vs/platform/audioCues/browser/audioCueService'; +import { AudioCue, AudioCueGroupId, IAudioCueService, Sound } from 'vs/platform/audioCues/browser/audioCueService'; import { LogService } from 'vs/platform/log/common/logService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -1055,6 +1055,11 @@ class StandaloneAudioService implements IAudioCueService { async playSound(cue: Sound, allowManyInParallel?: boolean | undefined): Promise { } + playAudioCueLoop(cue: AudioCue): IDisposable { + return toDisposable(() => { }); + } + playRandomAudioCue(groupId: AudioCueGroupId, allowManyInParallel?: boolean): void { + } } export interface IEditorOverrideServices { diff --git a/src/vs/editor/test/common/model/intervalTree.test.ts b/src/vs/editor/test/common/model/intervalTree.test.ts index 4b19bee62a0..bb94b0db456 100644 --- a/src/vs/editor/test/common/model/intervalTree.test.ts +++ b/src/vs/editor/test/common/model/intervalTree.test.ts @@ -17,7 +17,7 @@ const MAX_INSERTS = 30; const MIN_CHANGE_CNT = 10; const MAX_CHANGE_CNT = 20; -suite('IntervalTree', () => { +suite('IntervalTree 1', () => { class Interval { _intervalBrand: void = undefined; @@ -554,7 +554,7 @@ suite('IntervalTree', () => { }); }); -suite('IntervalTree', () => { +suite('IntervalTree 2', () => { function assertNodeAcceptEdit(msg: string, nodeStart: number, nodeEnd: number, nodeStickiness: TrackedRangeStickiness, start: number, end: number, textLength: number, forceMoveMarkers: boolean, expectedNodeStart: number, expectedNodeEnd: number): void { const node = new IntervalNode('', nodeStart, nodeEnd); setNodeStickiness(node, nodeStickiness); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 07aec862a82..71cbeb1eca3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5389,6 +5389,43 @@ declare namespace monaco.editor { getPosition(): IOverlayWidgetPosition | null; } + /** + * A glyph margin widget renders in the editor glyph margin. + */ + export interface IGlyphMarginWidget { + /** + * Get a unique identifier of the glyph widget. + */ + getId(): string; + /** + * Get the dom node of the glyph widget. + */ + getDomNode(): HTMLElement; + /** + * Get the placement of the glyph widget. + */ + getPosition(): IGlyphMarginWidgetPosition; + } + + /** + * A position for rendering glyph margin widgets. + */ + export interface IGlyphMarginWidgetPosition { + /** + * The glyph margin lane where the widget should be shown. + */ + lane: GlyphMarginLane; + /** + * The priority order of the widget, used for determining which widget + * to render when there are multiple. + */ + zIndex: number; + /** + * The editor range that this widget applies to. + */ + range: IRange; + } + /** * Type of hit element with the mouse in the editor. */ @@ -5961,6 +5998,19 @@ declare namespace monaco.editor { * Remove an overlay widget. */ removeOverlayWidget(widget: IOverlayWidget): void; + /** + * Add a glyph margin widget. Widgets must have unique ids, otherwise they will be overwritten. + */ + addGlyphMarginWidget(widget: IGlyphMarginWidget): void; + /** + * Layout/Reposition a glyph margin widget. This is a ping to the editor to call widget.getPosition() + * and update appropriately. + */ + layoutGlyphMarginWidget(widget: IGlyphMarginWidget): void; + /** + * Remove a glyph margin widget. + */ + removeGlyphMarginWidget(widget: IGlyphMarginWidget): void; /** * Change the view zones. View zones are lost when a new model is attached to the editor. */ diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 811ccaac879..c08d3ab3548 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -88,6 +88,7 @@ export class MenuWorkbenchButtonBar extends ButtonBar { } btn.enabled = action.enabled; + btn.element.classList.add('default-colors'); if (conifgProvider(action)?.showLabel ?? true) { btn.label = action.label; } else { diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 90a707db13a..debebabccd8 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -22,6 +22,8 @@ export interface IAudioCueService { onEnabledChanged(cue: AudioCue): Event; playSound(cue: Sound, allowManyInParallel?: boolean): Promise; + playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable; + playRandomAudioCue(groupId: AudioCueGroupId, allowManyInParallel?: boolean): void; } export class AudioCueService extends Disposable implements IAudioCueService { @@ -51,6 +53,16 @@ export class AudioCueService extends Disposable implements IAudioCueService { await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); } + /** + * Gaming and other apps often play a sound variant when the same event happens again + * for an improved experience. This function plays a random sound from the given group to accomplish that. + */ + public playRandomAudioCue(groupId: AudioCueGroupId, allowManyInParallel?: boolean): void { + const cues = AudioCue.allAudioCues.filter(cue => cue.groupId === groupId); + const index = Math.floor(Math.random() * cues.length); + this.playAudioCue(cues[index], allowManyInParallel); + } + private getVolumeInPercent(): number { const volume = this.configurationService.getValue('audioCues.volume'); if (typeof volume !== 'number') { @@ -66,7 +78,6 @@ export class AudioCueService extends Disposable implements IAudioCueService { if (!allowManyInParallel && this.playingSounds.has(sound)) { return; } - this.playingSounds.add(sound); const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(true); @@ -87,6 +98,23 @@ export class AudioCueService extends Disposable implements IAudioCueService { } } + public playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable { + let playing = true; + const playSound = () => { + if (playing) { + this.playAudioCue(cue, true).finally(() => { + setTimeout(() => { + if (playing) { + playSound(); + } + }, milliseconds); + }); + } + }; + playSound(); + return toDisposable(() => playing = false); + } + private readonly obsoleteAudioCuesEnabled = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled') @@ -190,19 +218,30 @@ export class Sound { public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); + public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); + public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); + public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); + public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); + public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); + public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); + public static readonly chatResponseReceived5 = Sound.register({ fileName: 'chatResponseReceived5.mp3' }); private constructor(public readonly fileName: string) { } } +export const enum AudioCueGroupId { + chatResponseReceived = 'chatResponseReceived' +} + export class AudioCue { private static _audioCues = new Set(); - private static register(options: { name: string; sound: Sound; settingsKey: string; + groupId?: AudioCueGroupId; }): AudioCue { - const audioCue = new AudioCue(options.sound, options.name, options.settingsKey); + const audioCue = new AudioCue(options.sound, options.name, options.settingsKey, options.groupId); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -309,9 +348,53 @@ export class AudioCue { settingsKey: 'audioCues.diffLineModified' }); + public static readonly chatRequestSent = AudioCue.register({ + name: localize('audioCues.chatRequestSent', 'Chat Request Sent'), + sound: Sound.chatRequestSent, + settingsKey: 'audioCues.chatRequestSent' + }); + + private static readonly chatResponseReceived = { + name: localize('audioCues.chatResponseReceived', 'Chat Response Received'), + settingsKey: 'audioCues.chatResponseReceived', + groupId: AudioCueGroupId.chatResponseReceived + }; + + public static readonly chatResponseReceived1 = AudioCue.register({ + sound: Sound.chatResponseReceived1, + ...this.chatResponseReceived + }); + + public static readonly chatResponseReceived2 = AudioCue.register({ + sound: Sound.chatResponseReceived2, + ...this.chatResponseReceived + }); + + public static readonly chatResponseReceived3 = AudioCue.register({ + sound: Sound.chatResponseReceived3, + ...this.chatResponseReceived + }); + + public static readonly chatResponseReceived4 = AudioCue.register({ + sound: Sound.chatResponseReceived4, + ...this.chatResponseReceived + }); + + public static readonly chatResponseReceived5 = AudioCue.register({ + sound: Sound.chatResponseReceived5, + ...this.chatResponseReceived + }); + + public static readonly chatResponsePending = AudioCue.register({ + name: localize('audioCues.chatResponsePending', 'Chat Response Pending'), + sound: Sound.chatResponsePending, + settingsKey: 'audioCues.chatResponsePending' + }); + private constructor( public readonly sound: Sound, public readonly name: string, public readonly settingsKey: string, + public readonly groupId?: string ) { } } diff --git a/src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 b/src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 new file mode 100644 index 00000000000..ac0791ef6bb Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 differ diff --git a/src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 b/src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 new file mode 100644 index 00000000000..670fdef74ee Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 differ diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 b/src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 new file mode 100644 index 00000000000..670fdef74ee Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 differ diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 b/src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 new file mode 100644 index 00000000000..c082927ecc1 Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 differ diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 b/src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 new file mode 100644 index 00000000000..99b75cda978 Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 differ diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 b/src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 new file mode 100644 index 00000000000..f1ab44a2616 Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 differ diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived5.mp3 b/src/vs/platform/audioCues/browser/media/chatResponseReceived5.mp3 new file mode 100644 index 00000000000..134edeb93af Binary files /dev/null and b/src/vs/platform/audioCues/browser/media/chatResponseReceived5.mp3 differ diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 05e07a1543d..97a5cb2ce5b 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -26,6 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface ICommandQuickPick extends IPickerQuickAccessItem { readonly commandId: string; readonly commandAlias?: string; + readonly args?: any[]; } export interface ICommandsQuickAccessOptions extends IPickerQuickAccessProviderOptions { @@ -211,7 +212,9 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc // Run try { - await this.commandService.executeCommand(commandPick.commandId); + commandPick.args?.length + ? await this.commandService.executeCommand(commandPick.commandId, ...commandPick.args) + : await this.commandService.executeCommand(commandPick.commandId); } catch (error) { if (!isCancellationError(error)) { this.dialogService.error(localize('canNotRun', "Command '{0}' resulted in an error", commandPick.label), toErrorMessage(error)); diff --git a/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts index 9b73eece6ba..e3cee9778a2 100644 --- a/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts @@ -165,7 +165,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ } async startTunnel(session: IRemoteTunnelSession): Promise { - if (isSameSession(session, this._session)) { + if (isSameSession(session, this._session) && this._tunnelStatus.type !== 'disconnected') { return this._tunnelStatus; } this.setSession(session); @@ -195,7 +195,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ } }; try { - await this.runCodeTunneCommand('stop', ['kill'], onOutput); + await this.runCodeTunnelCommand('stop', ['kill'], onOutput); } catch (e) { this._logger.error(e); } @@ -213,26 +213,35 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ } let isAttached = false; + let output = ''; const onOutput = (a: string, isErr: boolean) => { if (isErr) { this._logger.error(a); } else { - this._logger.info(a); + output += a; } if (!this.environmentService.isBuilt && a.startsWith(' Compiling')) { this.setTunnelStatus(TunnelStates.connecting(localize('remoteTunnelService.building', 'Building CLI from sources'))); } }; - const statusProcess = this.runCodeTunneCommand('status', ['status'], onOutput); + const statusProcess = this.runCodeTunnelCommand('status', ['status'], onOutput); this._tunnelProcess = statusProcess; try { - const status = await statusProcess; + await statusProcess; if (this._tunnelProcess !== statusProcess) { return; } - isAttached = status === 0; + + // split and find the line, since in dev builds additional noise is + // added by cargo to the output. + const status: { + service_installed: boolean; + tunnel: object | null; + } = JSON.parse(output.trim().split('\n').find(l => l.startsWith('{'))!); + + isAttached = !!status.tunnel; this._logger.info(isAttached ? 'Other tunnel running, attaching...' : 'No other tunnel running'); if (!isAttached && !this._session) { this._tunnelProcess = undefined; @@ -255,7 +264,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ a = a.replaceAll(token, '*'.repeat(4)); onOutput(a, isErr); }; - const loginProcess = this.runCodeTunneCommand('login', ['user', 'login', '--provider', session.providerId, '--access-token', token, '--log', LogLevelToString(this._logger.getLevel())], onLoginOutput); + const loginProcess = this.runCodeTunnelCommand('login', ['user', 'login', '--provider', session.providerId, '--access-token', token, '--log', LogLevelToString(this._logger.getLevel())], onLoginOutput); this._tunnelProcess = loginProcess; try { await loginProcess; @@ -286,7 +295,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ if (this._preventSleep()) { args.push('--no-sleep'); } - const serveCommand = this.runCodeTunneCommand('tunnel', args, (message: string, isErr: boolean) => { + const serveCommand = this.runCodeTunnelCommand('tunnel', args, (message: string, isErr: boolean) => { if (isErr) { this._logger.error(message); } else { @@ -315,7 +324,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ }); } - private runCodeTunneCommand(logLabel: string, commandArgs: string[], onOutput: (message: string, isError: boolean) => void = () => { }): CancelablePromise { + private runCodeTunnelCommand(logLabel: string, commandArgs: string[], onOutput: (message: string, isError: boolean) => void = () => { }): CancelablePromise { return createCancelablePromise(token => { return new Promise((resolve, reject) => { if (token.isCancellationRequested) { diff --git a/src/vs/platform/secrets/common/secrets.ts b/src/vs/platform/secrets/common/secrets.ts index 815fb358e9e..e2b54212a82 100644 --- a/src/vs/platform/secrets/common/secrets.ts +++ b/src/vs/platform/secrets/common/secrets.ts @@ -5,10 +5,15 @@ import { SequencerByKey } from 'vs/base/common/async'; import { IEncryptionService } from 'vs/platform/encryption/common/encryptionService'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, InMemoryStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Event, PauseableEmitter } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; +import { isNative } from 'vs/base/common/platform'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { localize } from 'vs/nls'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { once } from 'vs/base/common/functional'; export const ISecretStorageService = createDecorator('secretStorageService'); @@ -40,7 +45,9 @@ export class SecretStorageService implements ISecretStorageService { constructor( @IStorageService private _storageService: IStorageService, @IEncryptionService private _encryptionService: IEncryptionService, - @ILogService private readonly _logService: ILogService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @INotificationService private readonly _notificationService: INotificationService, + @ILogService private readonly _logService: ILogService ) { this._storageService.onDidChangeValue(e => this.onDidChangeValue(e.key)); } @@ -89,6 +96,10 @@ export class SecretStorageService implements ISecretStorageService { return this._sequencer.queue(key, async () => { await this.initialized; + if (isNative && this.type !== 'persisted') { + this.notifyNativeUserOnce(); + } + const encrypted = await this._encryptionService.encrypt(value); const fullKey = this.getKey(key); try { @@ -129,4 +140,20 @@ export class SecretStorageService implements ISecretStorageService { private getKey(key: string): string { return `${this._storagePrefix}${key}`; } + + private notifyNativeUserOnce = once(() => this.notifyNativeUser()); + private notifyNativeUser(): void { + this._notificationService.prompt( + Severity.Warning, + localize('notEncrypted', 'Secrets are not being stored on disk because encryption is not available in this environment.'), + [{ + label: localize('openTroubleshooting', "Open Troubleshooting"), + run: () => this._instantiationService.invokeFunction(accessor => { + const openerService = accessor.get(IOpenerService); + // Open troubleshooting docs page + return openerService.open('https://go.microsoft.com/fwlink/?linkid=2239490'); + }) + }] + ); + } } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 3819f7741df..54468f5862c 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -51,14 +51,14 @@ export function traceRpc(_target: any, key: string, descriptor: any) { descriptor[fnKey!] = async function (...args: any[]) { if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { - this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fnKey}(${args.map(e => JSON.stringify(e)).join(', ')})`); + this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fn!.name}(${args.map(e => JSON.stringify(e)).join(', ')})`); } if (this.traceRpcArgs.simulatedLatency) { await timeout(this.traceRpcArgs.simulatedLatency); } const result = await fn!.apply(this, args); if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { - this.traceRpcArgs.logService.trace(`[RPC Response] PtyService#${fnKey}`, result); + this.traceRpcArgs.logService.trace(`[RPC Response] PtyService#${fn!.name}`, result); } return result; }; diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index 86fa775403e..5a2d9943ac9 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -22,6 +22,7 @@ import * as platform from 'vs/base/common/platform'; import { createRegExp, escapeRegExpCharacters } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; +import { getOSReleaseInfo } from 'vs/base/node/osReleaseInfo'; import { findFreePort } from 'vs/base/node/ports'; import { addUNCHostToAllowlist, disableUNCAccessRestrictions } from 'vs/base/node/unc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; @@ -788,7 +789,7 @@ export async function createServer(address: string | net.AddressInfo | null, arg const vscodeServerListenTime: number = (global).vscodeServerListenTime; const vscodeServerCodeLoadedTime: number = (global).vscodeServerCodeLoadedTime; - instantiationService.invokeFunction((accessor) => { + instantiationService.invokeFunction(async (accessor) => { const telemetryService = accessor.get(ITelemetryService); type ServerStartClassification = { @@ -811,6 +812,30 @@ export async function createServer(address: string | net.AddressInfo | null, arg codeLoadedTime: vscodeServerCodeLoadedTime, readyTime: currentTime }); + + if (platform.isLinux) { + const logService = accessor.get(ILogService); + const releaseInfo = await getOSReleaseInfo(logService.error.bind(logService)); + if (releaseInfo) { + type ServerPlatformInfoClassification = { + platformId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system without any version information.' }; + platformVersionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system version excluding any name information or release code.' }; + platformIdLike: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system the current OS derivate is closely related to.' }; + owner: 'deepak1556'; + comment: 'Provides insight into the distro information on Linux.'; + }; + type ServerPlatformInfoEvent = { + platformId: string; + platformVersionId: string | undefined; + platformIdLike: string | undefined; + }; + telemetryService.publicLog2('serverPlatformInfo', { + platformId: releaseInfo.id, + platformVersionId: releaseInfo.version_id, + platformIdLike: releaseInfo.id_like + }); + } + } }); if (args['print-startup-performance']) { diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 741ea086118..0d73d9b454e 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -49,7 +49,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { disposables.add(this._notebookService.registerNotebookSerializer(viewType, extension, { options, dataToNotebook: async (data: VSBuffer): Promise => { - const sw = new StopWatch(true); + const sw = new StopWatch(); const dto = await this._proxy.$dataToNotebook(handle, data, CancellationToken.None); const result = NotebookDto.fromNotebookDataDto(dto.value); this._logService.trace(`[NotebookSerializer] dataToNotebook DONE after ${sw.elapsed()}ms`, { @@ -59,7 +59,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { return result; }, notebookToData: (data: NotebookData): Promise => { - const sw = new StopWatch(true); + const sw = new StopWatch(); const result = this._proxy.$notebookToData(handle, new SerializableObjectWithBuffers(NotebookDto.toNotebookDataDto(data)), CancellationToken.None); this._logService.trace(`[NotebookSerializer] notebookToData DONE after ${sw.elapsed()}`, { viewType, diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index c00e070b0f7..cedec7ba50a 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -391,7 +391,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape const COUNT = 2; let sum = 0; for (let i = 0; i < COUNT; i++) { - const sw = StopWatch.create(true); + const sw = StopWatch.create(); await this._proxy.$acceptProcessRequestLatency(terminalId); sw.stop(); sum += sw.elapsed(); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 8333762f6a7..f578d4b61b8 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -982,7 +982,7 @@ class CompletionsAdapter { const replaceRange = doc.getWordRangeAtPosition(pos) || new Range(pos, pos); const insertRange = replaceRange.with({ end: pos }); - const sw = new StopWatch(true); + const sw = new StopWatch(); const itemsOrList = await this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context)); if (!itemsOrList) { diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 65417c948d3..b83a462131d 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -83,7 +83,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { const fullSource = `${source}\n//# sourceURL=${sourceURL}`; let initFn: Function; try { - initFn = new Function('module', 'exports', 'require', fullSource); + initFn = new Function('module', 'exports', 'require', fullSource); // CodeQL [SM01632] js/eval-call there is no alternative until we move to ESM } catch (err) { if (extensionId) { console.error(`Loading code for extension ${extensionId} failed: ${err.message}`); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index f8d1715e921..35de24122ec 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -358,7 +358,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'boolean', tags: ['experimental'], 'description': localize('useSemanticSimilarity', "Controls whether the command palette should include similar commands. You must have an extension installed that provides Semantic Similarity."), - 'default': false + 'default': true }, 'workbench.quickOpen.closeOnFocusLost': { 'type': 'boolean', diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 8288b232880..1688aaa2d1d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -60,7 +60,7 @@ class AccessibleView extends Disposable { contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID, 'editor.contrib.selectionAnchorController']) }; const editorOptions: IEditorConstructionOptions = { - ...getSimpleEditorOptions(), + ...getSimpleEditorOptions(this._configurationService), lineDecorationsWidth: 6, dragAndDrop: true, cursorWidth: 1, @@ -70,8 +70,6 @@ class AccessibleView extends Disposable { quickSuggestions: false, renderWhitespace: 'none', dropIntoEditor: { enabled: true }, - accessibilitySupport: this._configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), - cursorBlinking: this._configurationService.getValue('terminal.integrated.cursorBlinking'), readOnly: true }; this._editorWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions)); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 61153542d54..0b767b35e90 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -117,6 +117,21 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), ...audioCueFeatureBase, }, + 'audioCues.chatRequestSent': { + 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), + ...audioCueFeatureBase, + default: 'off' + }, + 'audioCues.chatResponsePending': { + 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), + ...audioCueFeatureBase, + default: 'off' + }, + 'audioCues.chatResponseReceived': { + 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), + ...audioCueFeatureBase, + default: 'off' + } } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 0c583da7ee9..7691cf8fd9c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -24,9 +24,10 @@ import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/comm import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion'; + export function registerChatQuickQuestionActions() { registerAction2(AskQuickQuestionAction); } @@ -41,15 +42,14 @@ class AskQuickQuestionAction extends Action2 { constructor() { super({ - id: 'chat.action.askQuickQuestion', + id: ASK_QUICK_QUESTION_ACTION_ID, title: { value: localize('askQuickQuestion', "Ask Quick Question"), original: 'Ask Quick Question' }, precondition: CONTEXT_PROVIDER_EXISTS, - f1: true, + f1: false, category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, - when: ContextKeyExpr.equals('config.chat.experimental.quickQuestion.enable', true), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI } }); } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d8130d27601..f0c600dcbd4 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -22,11 +22,11 @@ import { registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/ac import { registerChatQuickQuestionActions } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatContributionService } from 'vs/workbench/contrib/chat/browser/chatContributionServiceImpl'; import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; -import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatAccessibilityService, ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -70,12 +70,6 @@ configurationRegistry.registerConfiguration({ type: 'number', description: nls.localize('interactiveSession.editor.lineHeight', "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size."), default: 0 - }, - 'chat.experimental.quickQuestion.enable': { - type: 'boolean', - description: nls.localize('interactiveSession.experimental.quickQuestion.enable', "Controls whether the quick question feature is enabled."), - default: false, - tags: ['experimental'] } } }); @@ -136,5 +130,6 @@ registerClearActions(); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed); registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); +registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ba977100111..9d8047338f8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const IChatWidgetService = createDecorator('chatWidgetService'); +export const IChatAccessibilityService = createDecorator('chatAccessibilityService'); export interface IChatWidgetService { @@ -29,6 +30,13 @@ export interface IChatWidgetService { getWidgetByInputUri(uri: URI): IChatWidget | undefined; } + +export interface IChatAccessibilityService { + readonly _serviceBrand: undefined; + acceptRequest(): void; + acceptResponse(response?: IChatResponseViewModel): void; +} + export interface IChatCodeBlockInfo { codeBlockIndex: number; element: IChatResponseViewModel; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index b7d1d8d7611..bcba9122486 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -190,7 +190,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge historyNavigationForwardsEnablement.set(enabled); }; - const options = getSimpleEditorOptions(); + const options = getSimpleEditorOptions(this.configurationService); options.readOnly = false; options.ariaLabel = this._getAriaLabel(); options.fontFamily = DEFAULT_FONT_FAMILY; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index e0d4b740cf5..d770a649f36 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -396,7 +396,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer markdown.value.startsWith(`/${s.command} `)); const toRender = usedSlashCommand ? markdown.value.slice(usedSlashCommand.command.length + 2) : markdown.value; - markdown = new MarkdownString(toRender); + markdown = new MarkdownString(toRender, { + isTrusted: { + enabledCommands: ['vscode.open'] + }, + }); const codeblocks: IChatCodeBlockInfo[] = []; const result = this.renderer.render(markdown, { @@ -612,7 +616,7 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart { })); const editorElement = dom.append(this.element, $('.interactive-result-editor')); this.editor = this._register(scopedInstantiationService.createInstance(CodeEditorWidget, editorElement, { - ...getSimpleEditorOptions(), + ...getSimpleEditorOptions(this.configurationService), readOnly: true, lineNumbers: 'off', selectOnLineNumbers: true, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 0956e550ac8..8ed4825829d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { alert } from 'vs/base/browser/ui/aria/aria'; +import { status } from 'vs/base/browser/ui/aria/aria'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; @@ -16,6 +16,7 @@ import 'vs/css!./media/chat'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { AudioCue, AudioCueGroupId, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -23,7 +24,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IViewsService } from 'vs/workbench/common/views'; import { clearChatSession } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; -import { ChatTreeItem, IChatCodeBlockInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; @@ -115,6 +116,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatService private readonly chatService: IChatService, @IChatWidgetService chatWidgetService: IChatWidgetService, @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); @@ -388,18 +390,17 @@ export class ChatWidget extends Disposable implements IChatWidget { this.instantiationService.invokeFunction(clearChatSession, this); return; } - + this._chatAccessibilityService.acceptRequest(); const input = query ?? editorValue; const result = await this.chatService.sendRequest(this.viewModel.sessionId, input); + if (result) { this.inputPart.acceptInput(query); - result.responseCompletePromise.then(() => { + result.responseCompletePromise.then(async () => { + const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; - if (lastResponse) { - const errorDetails = lastResponse.errorDetails ? ` ${lastResponse.errorDetails.message}` : ''; - alert(lastResponse.response.value + errorDetails); - } + this._chatAccessibilityService.acceptResponse(lastResponse); }); } } @@ -429,7 +430,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } layout(height: number, width: number): void { - width = Math.min(width, 600); + width = Math.min(width, 850); this.bodyDimension = new dom.Dimension(width, height); const inputPartHeight = this.inputPart.layout(height, width); @@ -505,3 +506,30 @@ export class ChatWidgetService implements IChatWidgetService { ); } } + + +const CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS = 7000; +export class ChatAccessibilityService extends Disposable implements IChatAccessibilityService { + + declare readonly _serviceBrand: undefined; + + private _responsePendingAudioCue: IDisposable | undefined; + + constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService) { + super(); + } + acceptRequest(): void { + this._audioCueService.playAudioCue(AudioCue.chatRequestSent, true); + this._responsePendingAudioCue = this._audioCueService.playAudioCueLoop(AudioCue.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); + } + acceptResponse(response?: IChatResponseViewModel): void { + this._responsePendingAudioCue?.dispose(); + this._audioCueService.playRandomAudioCue(AudioCueGroupId.chatResponseReceived, true); + if (!response) { + return; + } + const errorDetails = response.errorDetails ? ` ${response.errorDetails.message}` : ''; + status(response.response.value + errorDetails); + } +} + diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 6796d8436a8..d156d968408 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .interactive-session { - max-width: 600px; + max-width: 850px; margin: auto; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index bf463408a20..c402fb22a42 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -21,7 +21,7 @@ import * as strings from 'vs/base/common/strings'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; -import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { status } from 'vs/base/browser/ui/aria/aria'; import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); @@ -374,7 +374,7 @@ export abstract class SimpleFindWidget extends Widget { } else { label = NLS_NO_RESULTS; } - alertFn(this._announceSearchResults(label, this.inputValue)); + status(this._announceSearchResults(label, this.inputValue)); this._matchesCount.appendChild(document.createTextNode(label)); this._foundMatch = !!count && count.resultCount > 0; this.updateButtons(this._foundMatch); diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index 29d6921a6b0..6acfd4495b7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -12,8 +12,9 @@ import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreve import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -export function getSimpleEditorOptions(): IEditorOptions { +export function getSimpleEditorOptions(configurationService: IConfigurationService): IEditorOptions { return { wordWrap: 'on', overviewRulerLanes: 0, @@ -39,7 +40,9 @@ export function getSimpleEditorOptions(): IEditorOptions { }, guides: { indentation: false - } + }, + accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), + cursorBlinking: configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking') }; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 9026bdbebc0..ddede253470 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -155,11 +155,9 @@ export class SuggestEnabledInput extends Widget { this.placeholderText = append(this.stylingContainer, $('.suggest-input-placeholder', undefined, options.placeholderText || '')); const editorOptions: IEditorConstructionOptions = mixin( - getSimpleEditorOptions(), + getSimpleEditorOptions(configurationService), getSuggestEnabledInputOptions(ariaLabel)); editorOptions.overflowWidgetsDomNode = options.overflowWidgetsDomNode; - editorOptions.accessibilitySupport = configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'); - editorOptions.cursorBlinking = configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking'); const scopedContextKeyService = this.getScopedContextKeyService(contextKeyService); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 6f3edc9f382..82f7cd850af 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -46,6 +46,7 @@ import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollable import { DomEmitter } from 'vs/base/browser/event'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { FileAccess } from 'vs/base/common/network'; +import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; class CommentsActionRunner extends ActionRunner { protected override async runAction(action: IAction, context: any[]): Promise { @@ -131,6 +132,9 @@ export class CommentNode extends Disposable { this.createHeader(this._commentDetailsContainer); this._body = document.createElement(`div`); this._body.classList.add('comment-body', MOUSE_CURSOR_TEXT_CSS_CLASS_NAME); + if (configurationService.getValue(COMMENTS_SECTION)?.maxHeight !== false) { + this._body.classList.add('comment-body-max-height'); + } this.createScroll(this._commentDetailsContainer, this._body); this.updateCommentBody(this.comment.body); diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index be456347977..803aa80a18e 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -39,6 +39,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: 'boolean', default: true, description: nls.localize('comments.visible', "Controls the visibility of the comments bar and comment threads in editors that have commenting ranges and comments. Comments are still accessible via the Comments view and will cause commenting to be toggled on in the same way running the command \"Comments: Toggle Editor Commenting\" toggles comments.") + }, + 'comments.maxHeight': { + type: 'boolean', + default: true, + description: nls.localize('comments.maxHeight', "Controls whether the comments widget scrolls or expands.") } } }); diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 8667a78a9a9..13be5cf92fb 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -136,6 +136,9 @@ .review-widget .body .review-comment .review-comment-contents .comment-body { padding-top: 4px; +} + +.review-widget .body .review-comment .review-comment-contents .comment-body-max-height { max-height: 20em; } diff --git a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts index f2f812b14d6..efc9d016415 100644 --- a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts +++ b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts @@ -7,6 +7,7 @@ export interface ICommentsConfiguration { openView: 'never' | 'file' | 'firstFile'; useRelativeTime: boolean; visible: boolean; + maxHeight: boolean; } export const COMMENTS_SECTION = 'comments'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index ce99af8f16d..48fd41a505f 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -291,7 +291,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private createEditorOptions(): IEditorOptions { const editorConfig = this._configurationService.getValue('editor'); - const options = getSimpleEditorOptions(); + const options = getSimpleEditorOptions(this._configurationService); options.fontSize = editorConfig.fontSize; options.fontFamily = editorConfig.fontFamily; options.lineHeight = editorConfig.lineHeight; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 19dc6cda053..f601bc21c89 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -649,15 +649,12 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { CONTEXT_IN_DEBUG_REPL.bindTo(this.scopedContextKeyService).set(true); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); - const options = getSimpleEditorOptions(); + const options = getSimpleEditorOptions(this.configurationService); options.readOnly = true; options.suggest = { showStatusBar: true }; const config = this.configurationService.getValue('debug'); options.acceptSuggestionOnEnter = config.console.acceptSuggestionOnEnter === 'on' ? 'on' : 'off'; options.ariaLabel = localize('debugConsole', "Debug Console"); - // We must respect some accessibility related settings - options.accessibilitySupport = this.configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'); - options.cursorBlinking = this.configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking'); this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 8b3b57cd302..21227772d42 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -7,12 +7,17 @@ z-index: 3; } +.monaco-editor .zone-widget-container.inside-selection { + background-color: var(--vscode-inlineChat-regionHighlight); +} + .monaco-editor .inline-chat { color: inherit; padding: 6px; border-radius: 6px; border: 1px solid var(--vscode-inlineChat-border); box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow); + background: var(--vscode-inlineChat-background); } /* body */ diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index cd2032636e1..6f999737c01 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -200,15 +200,17 @@ export class InlineChatController implements IEditorContribution { let widgetPosition: Position; if (initialRender) { widgetPosition = this._editor.getSelection().getEndPosition(); - this._zone.value.setMargins(widgetPosition); + this._zone.value.setContainerMargins(); + this._zone.value.setWidgetMargins(widgetPosition); } else { assertType(this._activeSession); assertType(this._strategy); widgetPosition = this._strategy.getWidgetPosition() ?? this._zone.value.position ?? this._activeSession.wholeRange.value.getEndPosition(); const needsMargin = this._strategy.needsMargin(); if (!needsMargin) { - this._zone.value.setMargins(widgetPosition, 0); + this._zone.value.setWidgetMargins(widgetPosition, 0); } + this._zone.value.updateBackgroundColor(widgetPosition, this._activeSession.wholeRange.value); } this._zone.value.show(widgetPosition); } @@ -221,7 +223,7 @@ export class InlineChatController implements IEditorContribution { } } - private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { + private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -253,6 +255,10 @@ export class InlineChatController implements IEditorContribution { createSessionCts.dispose(); msgListener.dispose(); + + if (createSessionCts.token.isCancellationRequested) { + return State.PAUSE; + } } delete options.initialRange; @@ -290,17 +296,23 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.clear(); - const wholeRangeDecoration = this._editor.createDecorationsCollection([{ - range: this._activeSession.wholeRange.value, - options: InlineChatController._decoBlock - }]); + const wholeRangeDecoration = this._editor.createDecorationsCollection(); + const updateWholeRangeDecoration = () => { + wholeRangeDecoration.set([{ + range: this._activeSession!.wholeRange.value, + options: InlineChatController._decoBlock + }]); + }; this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear())); + this._sessionStore.add(this._activeSession.wholeRange.onDidChange(updateWholeRangeDecoration)); + updateWholeRangeDecoration(); this._zone.value.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); this._zone.value.widget.placeholder = this._getPlaceholderText(); this._zone.value.widget.value = this._activeSession.lastInput?.value ?? this._zone.value.widget.value; this._zone.value.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._zone.value.widget.preferredExpansionState = this._activeSession.lastExpansionState; + this._showWidget(false); this._sessionStore.add(this._editor.onDidChangeModel((e) => { const msg = this._activeSession?.lastExchange @@ -615,7 +627,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.PAUSE]() { - assertType(this._activeSession); this._ctxDidEdit.reset(); this._ctxLastResponseType.reset(); @@ -756,17 +767,17 @@ export class InlineChatController implements IEditorContribution { } cancelSession() { - if (!this._strategy || !this._activeSession) { - return undefined; - } - - const changedText = this._activeSession.asChangedText(); - if (changedText && this._activeSession?.lastExchange?.response instanceof EditResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); + let result: string | undefined; + if (this._strategy && this._activeSession) { + const changedText = this._activeSession.asChangedText(); + if (changedText && this._activeSession?.lastExchange?.response instanceof EditResponse) { + this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); + } + result = changedText; } this._messages.fire(Message.CANCEL_SESSION); - return changedText; + return result; } unstashLastSession(): Session | undefined { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 5734d7b46a7..69cc2f686a2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -33,6 +33,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; +import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; export class InlineChatLivePreviewWidget extends ZoneWidget { @@ -85,8 +86,17 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { originalEditor: { contributions: diffContributions }, modifiedEditor: { contributions: diffContributions } }, editor); + this._disposables.add(this._diffEditor); this._diffEditor.setModel({ original: this._session.textModel0, modified: editor.getModel() }); + this._diffEditor.updateOptions({ + lineDecorationsWidth: editor.getLayoutInfo().decorationsWidth + }); + + const highlighter = WordHighlighterContribution.get(editor); + if (highlighter) { + this._disposables.add(highlighter.linkWordHighlighters(this._diffEditor.getModifiedEditor())); + } const doStyle = () => { const theme = themeService.getColorTheme(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index fde0ebbe2b8..d39710a45ce 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -25,6 +25,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isCancellationError } from 'vs/base/common/errors'; import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { raceCancellation } from 'vs/base/common/async'; export type Recording = { when: Date; @@ -66,20 +67,20 @@ class SessionWholeRange { private static readonly _options = { description: 'inlineChat/session/wholeRange' }; - private readonly _store = new DisposableStore(); + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + private readonly _decorationIds: string[] = []; constructor(private readonly _textModel: ITextModel, wholeRange: IRange) { this._decorationIds = _textModel.deltaDecorations([], [{ range: wholeRange, options: SessionWholeRange._options }]); - this._store.add(toDisposable(() => { - if (!_textModel.isDisposed()) { - _textModel.deltaDecorations(this._decorationIds, []); - } - })); } dispose() { - this._store.dispose(); + this._onDidChange.dispose(); + if (!this._textModel.isDisposed()) { + this._textModel.deltaDecorations(this._decorationIds, []); + } } trackEdits(edits: ISingleEditOperation[]): void { @@ -88,6 +89,7 @@ class SessionWholeRange { newDeco.push({ range: edit.range, options: SessionWholeRange._options }); } this._decorationIds.push(...this._textModel.deltaDecorations([], newDeco)); + this._onDidChange.fire(this); } get value(): Range { @@ -402,7 +404,6 @@ export class InlineChatSessionService implements IInlineChatSessionService { this._sessions.clear(); } - async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { const provider = Iterable.first(this._inlineChatService.getAllProvider()); @@ -417,7 +418,10 @@ export class InlineChatSessionService implements IInlineChatSessionService { const selection = editor.getSelection(); let raw: IInlineChatSession | undefined | null; try { - raw = await provider.prepareInlineChatSession(textModel, selection, token); + raw = await raceCancellation( + Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)), + token + ); } catch (error) { this._logService.error('[IE] FAILED to prepare session', provider.debugName); this._logService.error(error); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 83b606f2d3b..36ecb9beb0a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -6,8 +6,8 @@ import 'vs/css!./inlineChat'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { Range } from 'vs/editor/common/core/range'; +import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -744,15 +744,15 @@ export class InlineChatZoneWidget extends ZoneWidget { protected override _doLayout(heightInPixel: number): void { const maxWidth = !this.widget.showsAnyPreview() ? 640 : Number.MAX_SAFE_INTEGER; - const width = Math.min(maxWidth, this._availableSpaceGivenIndentation()); + const width = Math.min(maxWidth, this._availableSpaceGivenIndentation(this._indentationWidth)); this._dimension = new Dimension(width, heightInPixel); this.widget.domNode.style.width = `${width}px`; this.widget.layout(this._dimension); } - private _availableSpaceGivenIndentation(): number { + private _availableSpaceGivenIndentation(indentationWidth: number | undefined): number { const info = this.editor.getLayoutInfo(); - return info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth + (this._indentationWidth ?? 0)); + return info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth + (indentationWidth ?? 0)); } private _computeHeightInLines(): number { @@ -773,6 +773,18 @@ export class InlineChatZoneWidget extends ZoneWidget { this._ctxVisible.set(true); } + protected override _getWidth(info: EditorLayoutInfo): number { + return info.width - info.minimap.minimapWidth; + } + + updateBackgroundColor(position: Position, selection: IRange) { + if (!this.container) { + return; + } + const widgetLineNumber = position.lineNumber; + this.container.classList.toggle('inside-selection', widgetLineNumber >= selection.startLineNumber && widgetLineNumber < selection.endLineNumber); + } + private _calculateIndentationWidth(position: Position): number { const viewModel = this.editor._getViewModel(); if (!viewModel) { @@ -794,26 +806,29 @@ export class InlineChatZoneWidget extends ZoneWidget { return this.editor.getOffsetForColumn(indentationLineNumber ?? positionLine, indentationLevel ?? viewModel.getLineFirstNonWhitespaceColumn(positionLine)); } - setMargins(position: Position, indentationWidth?: number): void { + setContainerMargins(): void { + if (!this.container) { + return; + } + const info = this.editor.getLayoutInfo(); + const marginWithoutIndentation = info.glyphMarginWidth + info.decorationsWidth + info.lineNumbersWidth; + this.container.style.marginLeft = `${marginWithoutIndentation}px`; + } + + setWidgetMargins(position: Position, indentationWidth?: number): void { if (indentationWidth === undefined) { indentationWidth = this._calculateIndentationWidth(position); } if (this._indentationWidth === indentationWidth) { return; } - this._indentationWidth = indentationWidth; - const info = this.editor.getLayoutInfo(); - const marginWithoutIndentation = info.glyphMarginWidth + info.decorationsWidth + info.lineNumbersWidth; - const marginWithIndentation = marginWithoutIndentation + this._indentationWidth; - const isEnoughAvailableSpaceWithIndentation = this._availableSpaceGivenIndentation() > 400; - this._indentationWidth = isEnoughAvailableSpaceWithIndentation ? this._indentationWidth : 0; - const spaceLeft = isEnoughAvailableSpaceWithIndentation ? marginWithIndentation : marginWithoutIndentation; - const spaceRight = info.minimap.minimapWidth + info.verticalScrollbarWidth; - this.widget.domNode.style.marginLeft = `${spaceLeft}px`; - this.widget.domNode.style.marginRight = `${spaceRight}px`; + this._indentationWidth = this._availableSpaceGivenIndentation(indentationWidth) > 400 ? indentationWidth : 0; + this.widget.domNode.style.marginLeft = `${this._indentationWidth}px`; + this.widget.domNode.style.marginRight = `${this.editor.getLayoutInfo().minimap.minimapWidth}px`; } override hide(): void { + this.container!.classList.remove('inside-selection'); this._ctxVisible.reset(); this._ctxCursorPosition.reset(); this.widget.reset(); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 527416b8868..4b8d1f12789 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -16,7 +16,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; export interface IInlineChatSlashCommand { @@ -134,6 +134,8 @@ export const MENU_INLINE_CHAT_WIDGET_DISCARD = MenuId.for('inlineChatWidget.undo // --- colors + +export const inlineChatBackground = registerColor('inlineChat.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, localize('inlineChat.background', "Background color of the interactive editor widget")); export const inlineChatBorder = registerColor('inlineChat.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChat.border', "Border color of the interactive editor widget")); export const inlineChatShadow = registerColor('inlineChat.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, localize('inlineChat.shadow', "Shadow color of the interactive editor widget")); export const inlineChatRegionHighlight = registerColor('inlineChat.regionHighlight', { dark: editorHoverHighlight, light: editorHoverHighlight, hcDark: editorHoverHighlight, hcLight: editorHoverHighlight }, localize('inlineChat.regionHighlight', "Background highlighting of the current interactive region. Must be transparent."), true); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index faf374d8746..29e8329ac80 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -276,7 +276,7 @@ export class InteractiveEditor extends EditorPane { overrideIdentifier = this.#codeEditorWidget.getModel()?.getLanguageId(); } const editorOptions = deepClone(this.#configurationService.getValue('editor', { overrideIdentifier })); - const editorOptionsOverride = getSimpleEditorOptions(); + const editorOptionsOverride = getSimpleEditorOptions(this.#configurationService); const computed = Object.freeze({ ...editorOptions, ...editorOptionsOverride, @@ -670,6 +670,7 @@ export class InteractiveEditor extends EditorPane { } override focus() { + this.#notebookWidget.value?.onShow(); this.#codeEditorWidget.focus(); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 237532de8d0..e45dc8e343c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1913,6 +1913,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } + onShow() { + this._isVisible = true; + } + private focusEditor(activeElement: CellViewModel): void { for (const [element, editor] of this._renderedEditors.entries()) { if (element === activeElement) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index b1404793190..b0a6bc62001 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -1132,6 +1132,9 @@ export function workbenchCalculateActions(initialPrimaryActions: IActionModel[], if (itemSize !== 0) { nonZeroAction = true; } + if (actionModel.action instanceof Separator) { + nonZeroAction = false; + } } else { containerFull = true; if (itemSize === 0) { // size 0 implies a hidden item, keep in primary to allow for Workbench to handle visibility diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts index 4faad5f11c5..4df6550371b 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts @@ -25,6 +25,16 @@ interface IActionModel { * ex: action with size 50 requires 58px of space */ suite('workbenchCalculateActions', () => { + + const defaultSecondaryActionModels: IActionModel[] = [ + { action: new Action('secondaryAction0', 'Secondary Action 0'), size: 50, visible: true, renderLabel: true }, + { action: new Action('secondaryAction1', 'Secondary Action 1'), size: 50, visible: true, renderLabel: true }, + { action: new Action('secondaryAction2', 'Secondary Action 2'), size: 50, visible: true, renderLabel: true }, + ]; + const defaultSecondaryActions: IAction[] = defaultSecondaryActionModels.map(action => action.action); + const separator: IActionModel = { action: new Separator(), size: 1, visible: true, renderLabel: true }; + + test('should return empty primary and secondary actions when given empty initial actions', () => { const result = workbenchCalculateActions([], [], 100); assert.deepEqual(result.primaryActions, []); @@ -37,20 +47,20 @@ suite('workbenchCalculateActions', () => { { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, { action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true }, ]; - const result = workbenchCalculateActions(actions, [], 200); + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 200); assert.deepEqual(result.primaryActions, actions.map(action => action.action)); - assert.deepEqual(result.secondaryActions, []); + assert.deepEqual(result.secondaryActions, defaultSecondaryActions); }); - test.skip('should move actions to secondary when they do not fit within the container width', () => { + test('should move actions to secondary when they do not fit within the container width', () => { const actions: IActionModel[] = [ { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, { action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true }, ]; - const result = workbenchCalculateActions(actions, [], 100); + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 100); assert.deepEqual(result.primaryActions, [actions[0]].map(action => action.action)); - assert.deepEqual(result.secondaryActions, [actions[1], actions[2]].map(action => action.action)); + assert.deepEqual(result.secondaryActions, [actions[1], actions[2], separator, ...defaultSecondaryActionModels].map(action => action.action)); }); test('should ignore second separator when two separators are in a row', () => { @@ -60,9 +70,9 @@ suite('workbenchCalculateActions', () => { { action: new Separator(), size: 1, visible: true, renderLabel: true }, { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, ]; - const result = workbenchCalculateActions(actions, [], 125); + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 125); assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[3]].map(action => action.action)); - assert.deepEqual(result.secondaryActions, []); + assert.deepEqual(result.secondaryActions, defaultSecondaryActions); }); test('should ignore separators when they are at the end of the resulting primary actions', () => { @@ -72,21 +82,21 @@ suite('workbenchCalculateActions', () => { { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, ]; - const result = workbenchCalculateActions(actions, [], 200); + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 200); assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[2]].map(action => action.action)); - assert.deepEqual(result.secondaryActions, []); + assert.deepEqual(result.secondaryActions, defaultSecondaryActions); }); - test.skip('should keep actions with size 0 in primary actions', () => { + test('should keep actions with size 0 in primary actions', () => { const actions: IActionModel[] = [ { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, { action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true }, { action: new Action('action3', 'Action 3'), size: 0, visible: true, renderLabel: true }, ]; - const result = workbenchCalculateActions(actions, [], 116); + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 116); assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[3]].map(action => action.action)); - assert.deepEqual(result.secondaryActions, [actions[2]].map(action => action.action)); + assert.deepEqual(result.secondaryActions, [actions[2], separator, ...defaultSecondaryActionModels].map(action => action.action)); }); test('should not render separator if preceeded by size 0 action(s).', () => { @@ -95,8 +105,22 @@ suite('workbenchCalculateActions', () => { { action: new Separator(), size: 1, visible: true, renderLabel: true }, { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, ]; - const result = workbenchCalculateActions(actions, [], 116); + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 116); assert.deepEqual(result.primaryActions, [actions[0], actions[2]].map(action => action.action)); - assert.deepEqual(result.secondaryActions, []); + assert.deepEqual(result.secondaryActions, defaultSecondaryActions); + }); + + test('should not render second separator if space between is hidden (size 0) actions.', () => { + const actions: IActionModel[] = [ + { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, + { action: new Separator(), size: 1, visible: true, renderLabel: true }, + { action: new Action('action1', 'Action 1'), size: 0, visible: true, renderLabel: true }, + { action: new Action('action2', 'Action 2'), size: 0, visible: true, renderLabel: true }, + { action: new Separator(), size: 1, visible: true, renderLabel: true }, + { action: new Action('action3', 'Action 3'), size: 50, visible: true, renderLabel: true }, + ]; + const result = workbenchCalculateActions(actions, defaultSecondaryActions, 300); + assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[2], actions[3], actions[5]].map(action => action.action)); + assert.deepEqual(result.secondaryActions, defaultSecondaryActions); }); }); diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 39fbc52fbdc..dd837a29ec7 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -35,8 +35,7 @@ import { isFirefox } from 'vs/base/browser/browser'; import { IProductService } from 'vs/platform/product/common/productService'; import { ISemanticSimilarityService } from 'vs/workbench/services/semanticSimilarity/common/semanticSimilarityService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -80,19 +79,10 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce ) { super({ showAlias: !Language.isDefaultVariant(), - noResultsPick: (filter) => { - const info = this.chatService.getProviderInfos()[0]; - return info - ? { - label: localize('askXInChat', "Ask {0}: {1}", info.displayName, filter), - commandId: AskInInteractiveAction.ID, - accept: () => commandService.executeCommand(AskInInteractiveAction.ID, filter) - } - : { - label: localize('noCommandResults', "No matching commands"), - commandId: '' - }; - }, + noResultsPick: () => ({ + label: localize('noCommandResults', "No matching commands"), + commandId: '' + }), }, instantiationService, keybindingService, commandService, telemetryService, dialogService); this._register(configurationService.onDidChangeConfiguration((e) => this.updateOptions(e))); @@ -175,12 +165,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce const sortedIndices = scores.map((_, i) => i).sort((a, b) => scores[b] - scores[a]); const setOfPicksSoFar = new Set(picksSoFar.map(p => p.commandId)); - const additionalPicks: Array = picksSoFar.length > 0 - ? [{ - type: 'separator', - label: localize('semanticSimilarity', "similar commands") - }] - : []; + const additionalPicks = new Array(); let numOfSmartPicks = 0; for (const i of sortedIndices) { @@ -196,6 +181,28 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce } } + if (numOfSmartPicks) { + additionalPicks.unshift({ + type: 'separator', + label: localize('semanticSimilarity', "similar commands") + }); + } + + if (picksSoFar.length || additionalPicks.length) { + additionalPicks.push({ + type: 'separator' + }); + } + + const info = this.chatService.getProviderInfos()[0]; + if (info) { + additionalPicks.push({ + label: localize('askXInChat', "Ask {0}: {1}", info.displayName, filter), + commandId: ASK_QUICK_QUESTION_ACTION_ID, + args: [filter] + }); + } + return additionalPicks; } @@ -299,48 +306,4 @@ export class ClearCommandHistoryAction extends Action2 { } } -// TODO: Should this live here? It seems fairly generic and could live in the interactive code. -export class AskInInteractiveAction extends Action2 { - - static readonly ID = 'workbench.action.askCommandInChat'; - - constructor() { - super({ - id: AskInInteractiveAction.ID, - title: { value: localize('askInChat', "Ask In Chat"), original: 'Ask In Chat' }, - f1: false - }); - } - - async run(accessor: ServicesAccessor, filter?: string): Promise { - const chatService = accessor.get(IChatService); - const chatWidgetService = accessor.get(IChatWidgetService); - const logService = accessor.get(ILogService); - - if (!filter) { - throw new Error('No filter provided.'); - } - - let providerId: string; - const providerInfos = chatService.getProviderInfos(); - switch (providerInfos.length) { - case 0: - throw new Error('No chat provider found.'); - case 1: - providerId = providerInfos[0].id; - break; - default: - logService.warn('Multiple chat providers found. Using the first one.'); - providerId = providerInfos[0].id; - break; - } - - const widget = await chatWidgetService.revealViewForProvider(providerId); - if (widget?.viewModel) { - // TODO: Maybe this could provide metadata saying it came from the command palette? - chatService.sendRequestToProvider(widget.viewModel.sessionId, { message: filter }); - } - } -} - //#endregion diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts index 19795f4c5f0..85a5e04afd2 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -8,7 +8,7 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/ import { Registry } from 'vs/platform/registry/common/platform'; import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; import { ViewQuickAccessProvider, OpenViewPickerAction, QuickAccessViewPickerAction } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; -import { CommandsQuickAccessProvider, ShowAllCommandsAction, ClearCommandHistoryAction, AskInInteractiveAction } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; +import { CommandsQuickAccessProvider, ShowAllCommandsAction, ClearCommandHistoryAction } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -112,7 +112,6 @@ registerAction2(ClearCommandHistoryAction); registerAction2(ShowAllCommandsAction); registerAction2(OpenViewPickerAction); registerAction2(QuickAccessViewPickerAction); -registerAction2(AskInInteractiveAction); const inViewsPickerContextKey = 'inViewsPicker'; const inViewsPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(inViewsPickerContextKey)); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index bc33ef351cb..f6278a4d872 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2005,9 +2005,6 @@ class SCMInputWidget { const fontFamily = this.getInputEditorFontFamily(); const fontSize = this.getInputEditorFontSize(); const lineHeight = this.computeLineHeight(fontSize); - // We must respect some accessibility related settings - const accessibilitySupport = this.configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'); - const cursorBlinking = this.configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking'); this.setPlaceholderFontStyles(fontFamily, fontSize, lineHeight); @@ -2015,7 +2012,7 @@ class SCMInputWidget { this.repositoryIdContextKey = contextKeyService2.createKey('scmRepository', undefined); const editorOptions: IEditorConstructionOptions = { - ...getSimpleEditorOptions(), + ...getSimpleEditorOptions(configurationService), lineDecorationsWidth: 6, dragAndDrop: true, cursorWidth: 1, @@ -2029,9 +2026,7 @@ class SCMInputWidget { scrollbar: { alwaysConsumeMouseWheel: false }, overflowWidgetsDomNode, renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - accessibilitySupport, - cursorBlinking + dropIntoEditor: { enabled: true } }; const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 2c434917669..68bdf40a0f0 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -35,6 +35,7 @@ display: flex; align-items: center; justify-content: center; + background-color: unset; } .monaco-workbench .search-view .search-widget .toggle-replace-button:hover { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 39ed50f4814..c3b6b486a9f 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -84,7 +84,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise { - const sw = new StopWatch(true); + const sw = new StopWatch(); const languageId = this._getLanguageIdAtPosition(model, position); const languageConfig = this._languageConfigurationService.getLanguageConfiguration(languageId); const snippets = new Set(await this._snippets.getSnippets(languageId)); diff --git a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css index d87b418ed89..2a1f155fc8e 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css +++ b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .editor-instance .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { +.monaco-workbench .xterm-viewport { /* Use the hack presented in https://stackoverflow.com/a/38748186/1156119 to get opacity transitions working on the scrollbar */ -webkit-background-clip: text; background-clip: text; @@ -12,45 +11,35 @@ transition: background-color 800ms linear; } -.monaco-workbench .editor-instance .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { +.monaco-workbench .xterm-viewport { scrollbar-width: thin; } -.monaco-workbench .editor-instance .xterm-viewport::-webkit-scrollbar, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar { +.monaco-workbench .xterm-viewport::-webkit-scrollbar { width: 10px; } -.monaco-workbench .editor-instance .xterm-viewport::-webkit-scrollbar-track, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar-track { +.monaco-workbench .xterm-viewport::-webkit-scrollbar-track { opacity: 0; } -.monaco-workbench .editor-instance .xterm-viewport::-webkit-scrollbar-thumb, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar-thumb { +.monaco-workbench .xterm-viewport::-webkit-scrollbar-thumb { min-height: 20px; background-color: inherit; } -.monaco-workbench .editor-instance .force-scrollbar .xterm .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .force-scrollbar .xterm .xterm-viewport, -.monaco-workbench .editor-instance .xterm.focus .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport, -.monaco-workbench .editor-instance .xterm:focus .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport, -.monaco-workbench .editor-instance .xterm:hover .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { +.monaco-workbench .force-scrollbar .xterm .xterm-viewport, +.monaco-workbench .xterm.focus .xterm-viewport, +.monaco-workbench .xterm:focus .xterm-viewport, +.monaco-workbench .xterm:hover .xterm-viewport { transition: opacity 100ms linear; cursor: default; } -.monaco-workbench .editor-instance .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover, -.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { +.monaco-workbench .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { transition: opacity 0ms linear; } -.monaco-workbench .editor-instance .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive, -.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { +.monaco-workbench .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { background-color: inherit; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 91c80d201f3..814626ed4f5 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -26,8 +26,8 @@ .monaco-workbench .pane-body.integrated-terminal .terminal-groups-container, .monaco-workbench .pane-body.integrated-terminal .terminal-group, .monaco-workbench .pane-body.integrated-terminal .terminal-split-pane, -.monaco-workbench .editor-instance .terminal-split-pane, -.monaco-workbench .editor-instance .terminal-outer-container { +.monaco-workbench .terminal-editor .terminal-split-pane, +.monaco-workbench .terminal-editor .terminal-outer-container { height: 100%; } .monaco-workbench .part.sidebar .pane-body.integrated-terminal .terminal-outer-container, @@ -48,7 +48,7 @@ background-color: var(--vscode-terminal-tab-activeBorder); } /* Override monaco's styles for terminal editors */ -.monaco-workbench .editor-instance .xterm textarea:focus { +.monaco-workbench .terminal-editor .xterm textarea:focus { opacity: 0 !important; outline: 0 !important; } @@ -62,20 +62,23 @@ background-image: none !important; } -.monaco-workbench .editor-instance .terminal-wrapper { +.monaco-workbench .terminal-editor .terminal-wrapper { background-color: var(--vscode-terminal-background, --vscode-editorPane-background); } -.monaco-workbench .editor-instance .terminal-wrapper, +.monaco-workbench .terminal-editor .terminal-wrapper, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper { display: block; height: 100%; box-sizing: border-box; } -.monaco-workbench .editor-instance .xterm, -.monaco-workbench .pane-body.integrated-terminal .xterm { +.monaco-workbench .xterm { /* All terminals have at least 10px left/right edge padding and 2 padding on the bottom (so underscores on last line are visible */ padding: 0 10px 2px; +} + +.monaco-workbench .terminal-editor .xterm, +.monaco-workbench .pane-body.integrated-terminal .xterm { /* Bottom align the terminal within the split pane */ position: absolute; bottom: 0; @@ -88,23 +91,23 @@ top: 0; } -.monaco-workbench .editor-instance .terminal-wrapper.fixed-dims .xterm, +.monaco-workbench .terminal-editor .terminal-wrapper.fixed-dims .xterm, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.fixed-dims .xterm { position: static; } -.monaco-workbench .editor-instance .xterm-viewport, +.monaco-workbench .terminal-editor .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { z-index: 30; } -.monaco-workbench .editor-instance .xterm-decoration-overview-ruler, +.monaco-workbench .terminal-editor .xterm-decoration-overview-ruler, .monaco-workbench .pane-body.integrated-terminal .xterm-decoration-overview-ruler { z-index: 31; /* Must be higher than .xterm-viewport */ pointer-events: none; } -.monaco-workbench .editor-instance .xterm-screen, +.monaco-workbench .terminal-editor .xterm-screen, .monaco-workbench .pane-body.integrated-terminal .xterm-screen { z-index: 31; } @@ -127,7 +130,7 @@ .xterm.xterm-cursor-pointer .xterm-screen { cursor: pointer; } .xterm.column-select.focus .xterm-screen { cursor: crosshair; } -.monaco-workbench .editor-instance .xterm { +.monaco-workbench .terminal-editor .xterm { padding-left: 20px !important; } @@ -136,34 +139,34 @@ padding-left: 20px !important; } -.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm, +.monaco-workbench .terminal-editor .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm, .monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm { padding-right: 20px; } -.monaco-workbench .editor-instance .xterm a:not(.xterm-invalid-link), +.monaco-workbench .terminal-editor .xterm a:not(.xterm-invalid-link), .monaco-workbench .pane-body.integrated-terminal .xterm a:not(.xterm-invalid-link) { /* To support message box sizing */ position: relative; } -.monaco-workbench .editor-instance .terminal-wrapper > div, +.monaco-workbench .terminal-editor .terminal-wrapper > div, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper > div { height: 100%; } -.monaco-workbench .editor-instance .xterm-viewport, +.monaco-workbench .terminal-editor .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { box-sizing: border-box; } -.monaco-workbench .editor-instance .terminal-wrapper.fixed-dims, +.monaco-workbench .terminal-editor .terminal-wrapper.fixed-dims, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.fixed-dims { /* The viewport should be positioned against this so it does't conflict with a fixed dimensions terminal horizontal scroll bar*/ position: relative; } -.monaco-workbench .editor-instance .terminal-wrapper:not(.fixed-dims) .xterm-viewport, +.monaco-workbench .terminal-editor .terminal-wrapper:not(.fixed-dims) .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper:not(.fixed-dims) .xterm-viewport { /* Override xterm.js' width as we want to size the viewport to fill the panel so the scrollbar is on the right edge */ width: auto !important; @@ -245,7 +248,7 @@ top: 0; } -.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-helper-textarea:focus { +.monaco-workbench .xterm .xterm-helper-textarea:focus { /* Override the general vscode style applies `opacity:1!important` to textareas */ opacity: 0 !important; } @@ -522,34 +525,26 @@ background-color: var(--vscode-terminal-hoverHighlightBackground); } -.monaco-workbench .editor-instance .force-scrollbar .xterm .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .force-scrollbar .xterm .xterm-viewport, -.monaco-workbench .editor-instance .xterm.focus .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport, -.monaco-workbench .editor-instance .xterm:focus .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport, -.monaco-workbench .editor-instance .xterm:hover .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { +.force-scrollbar .xterm .xterm-viewport, +.monaco-workbench .xterm.focus .xterm-viewport, +.monaco-workbench .xterm:focus .xterm-viewport, +.monaco-workbench .xterm:hover .xterm-viewport { background-color: var(--vscode-scrollbarSlider-background) !important; } -.monaco-workbench .editor-instance .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { +.monaco-workbench .xterm-viewport { scrollbar-color: var(--vscode-scrollbarSlider-background) transparent; } -.monaco-workbench .editor-instance .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover, -.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { +.monaco-workbench .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: var(--vscode-scrollbarSlider-hoverBackground); } -.monaco-workbench .editor-instance .xterm-viewport:hover, -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport:hover { +.monaco-workbench .xterm-viewport:hover { scrollbar-color: var(--vscode-scrollbarSlider-hoverBackground) transparent; } -.monaco-workbench .editor-instance .xterm .xterm-viewport::-webkit-scrollbar-thumb:active, -.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { +.monaco-workbench .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: var(--vscode-scrollbarSlider-activeBackground); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 9c508dd76c3..242786cc2fd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -139,11 +139,21 @@ export const enum TerminalConnectionState { Connected } +export interface IDetachedXTermOptions { + cols: number; + rows: number; + colorProvider: IXtermColorProvider; + capabilities?: ITerminalCapabilityStore; + readonly?: boolean; +} + export interface ITerminalService extends ITerminalInstanceHost { readonly _serviceBrand: undefined; /** Gets all terminal instances, including editor and terminal view (group) instances. */ readonly instances: readonly ITerminalInstance[]; + /** Gets detached terminal instances created via {@link createDetachedXterm}. */ + readonly detachedXterms: Iterable; configHelper: ITerminalConfigHelper; isProcessSupportRegistered: boolean; readonly connectionState: TerminalConnectionState; @@ -171,6 +181,13 @@ export interface ITerminalService extends ITerminalInstanceHost { */ createTerminal(options?: ICreateTerminalOptions): Promise; + /** + * Creates a detached xterm instance which is not attached to the DOM or + * tracked as a terminal instance. + * @params options The options to create the terminal with + */ + createDetachedXterm(options: IDetachedXTermOptions): Promise; + /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ @@ -213,7 +230,6 @@ export interface ITerminalService extends ITerminalInstanceHost { resolveLocation(location?: ITerminalLocationOptions): TerminalLocation | undefined; setNativeDelegate(nativeCalls: ITerminalServiceNativeDelegate): void; - toggleEscapeSequenceLogging(): Promise; getEditingTerminal(): ITerminalInstance | undefined; setEditingTerminal(instance: ITerminalInstance | undefined): void; @@ -223,8 +239,6 @@ export class TerminalLinkQuickPickEvent extends MouseEvent { } export interface ITerminalServiceNativeDelegate { getWindowCount(): Promise; - openDevTools(): Promise; - toggleDevTools(): Promise; } /** @@ -708,11 +722,6 @@ export interface ITerminalInstance { */ resetFocusContextKey(): void; - /** - * Select all text in the terminal. - */ - selectAll(): void; - /** * Focuses the terminal instance if it's able to (the xterm.js instance must exist). * @@ -845,16 +854,6 @@ export interface ITerminalInstance { */ toggleSizeToContentWidth(): Promise; - /** - * Toggles escape sequence logging in the devtools console. - */ - toggleEscapeSequenceLogging(): Promise; - - /** - * Sets whether escape seqeunce logging is enabled in the devtools console. - */ - setEscapeSequenceLogging(enable: boolean): void; - /** * Gets the initial current working directory, fetching it from the backend if required. */ @@ -947,7 +946,14 @@ export const enum XtermTerminalConstants { SearchHighlightLimit = 1000 } -export interface IXtermTerminal { +export interface IXtermAttachToElementOptions { + /** + * Whether GPU rendering should be enabled for this element, defaults to true. + */ + enableGpu: boolean; +} + +export interface IXtermTerminal extends IDisposable { /** * An object that tracks when commands are run and enables navigating and selecting between * them. @@ -962,6 +968,11 @@ export interface IXtermTerminal { readonly onDidChangeSelection: Event; readonly onDidChangeFindResults: Event<{ resultIndex: number; resultCount: number }>; + /** + * Event fired when focus enters (fires with true) or leaves (false) the terminal. + */ + readonly onDidChangeFocus: Event; + /** * Gets a view of the current texture atlas used by the renderers. */ @@ -972,6 +983,18 @@ export interface IXtermTerminal { */ readonly isStdinDisabled: boolean; + /** + * Whether the terminal is currently focused. + */ + readonly isFocused: boolean; + + /** + * Attached the terminal to the given element + * @param container Container the terminal will be rendered in + * @param options Additional options for mounting the terminal in an element + */ + attachToElement(container: HTMLElement, options?: Partial): void; + findResult?: { resultIndex: number; resultCount: number }; /** @@ -994,6 +1017,34 @@ export interface IXtermTerminal { */ getFont(): ITerminalFont; + /** + * Gets whether there's any terminal selection. + */ + hasSelection(): boolean; + + /** + * Clears any terminal selection. + */ + clearSelection(): void; + + /** + * Selects all terminal contents/ + */ + selectAll(): void; + + /** + * Copies the terminal selection. + * @param {boolean} copyAsHtml Whether to copy selection as HTML, defaults to false. + */ + copySelection(copyAsHtml?: boolean): void; + + /** + * Focuses the terminal. Warning: {@link ITerminalInstance.focus} should be + * preferred when dealing with terminal instances in order to get + * accessibility triggers. + */ + focus(): void; + /** Scroll the terminal buffer down 1 line. */ scrollDownLine(): void; /** Scroll the terminal buffer down 1 page. */ scrollDownPage(): void; /** Scroll the terminal buffer to the bottom. */ scrollToBottom(): void; @@ -1022,12 +1073,30 @@ export interface IXtermTerminal { */ getBufferReverseIterator(): IterableIterator; + /** + * Gets the buffer contents as HTML. + */ + getContentsAsHtml(): Promise; + /** * Refreshes the terminal after it has been moved. */ refresh(): void; } +export interface IDetachedXtermTerminal extends IXtermTerminal { + + /** + * Writes data to the terminal. + */ + write(data: string | Uint8Array): void; + + /** + * Resizes the terminal. + */ + resize(columns: number, rows: number): void; +} + export interface IInternalXtermTerminal { /** * Writes text directly to the terminal, bypassing the process. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 4623b8aad7f..567382feaf6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -32,7 +32,7 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; -import { Direction, ICreateTerminalOptions, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { Direction, ICreateTerminalOptions, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, ITerminalProfileResolverService, ITerminalProfileService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -60,6 +60,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { Iterable } from 'vs/base/common/iterator'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -179,6 +180,32 @@ export function registerActiveInstanceAction( }); } +/** + * A wrapper around {@link registerTerminalAction} that ensures an active terminal + * exists and provides it to the run function. + * + * This includes detached xterm terminals that are not managed by an {@link ITerminalInstance}. + */ +export function registerActiveXtermAction( + options: IAction2Options & { run: (activeTerminal: IXtermTerminal, accessor: ServicesAccessor, instance?: ITerminalInstance, args?: unknown) => void | Promise } +): IDisposable { + const originalRun = options.run; + return registerTerminalAction({ + ...options, + run: (c, accessor, args) => { + const activeDetached = Iterable.find(c.service.detachedXterms, d => d.isFocused); + if (activeDetached) { + return originalRun(activeDetached, accessor, undefined, args); + } + + const activeInstance = c.service.activeInstance; + if (activeInstance?.xterm) { + return originalRun(activeInstance.xterm, accessor, activeInstance, args); + } + } + }); +} + export interface ITerminalServicesCollection { service: ITerminalService; groupService: ITerminalGroupService; @@ -569,59 +596,59 @@ export function registerTerminalActions() { } }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ScrollDownLine, title: { value: localize('workbench.action.terminal.scrollDown', "Scroll Down (Line)"), original: 'Scroll Down (Line)' }, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.altBufferActive.negate()), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => activeInstance.scrollDownLine() + run: (xterm) => xterm.scrollDownLine() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ScrollDownPage, title: { value: localize('workbench.action.terminal.scrollDownPage', "Scroll Down (Page)"), original: 'Scroll Down (Page)' }, keybinding: { primary: KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyCode.PageDown }, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.altBufferActive.negate()), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => activeInstance.scrollDownPage() + run: (xterm) => xterm.scrollDownPage() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ScrollToBottom, title: { value: localize('workbench.action.terminal.scrollToBottom', "Scroll to Bottom"), original: 'Scroll to Bottom' }, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.End, linux: { primary: KeyMod.Shift | KeyCode.End }, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.altBufferActive.negate()), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => activeInstance.scrollToBottom() + run: (xterm) => xterm.scrollToBottom() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ScrollUpLine, title: { value: localize('workbench.action.terminal.scrollUp', "Scroll Up (Line)"), original: 'Scroll Up (Line)' }, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.altBufferActive.negate()), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => activeInstance.scrollUpLine() + run: (xterm) => xterm.scrollUpLine() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ScrollUpPage, title: { value: localize('workbench.action.terminal.scrollUpPage', "Scroll Up (Page)"), original: 'Scroll Up (Page)' }, f1: true, @@ -629,38 +656,38 @@ export function registerTerminalActions() { keybinding: { primary: KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyCode.PageUp }, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.altBufferActive.negate()), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => activeInstance.scrollUpPage() + run: (xterm) => xterm.scrollUpPage() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ScrollToTop, title: { value: localize('workbench.action.terminal.scrollToTop', "Scroll to Top"), original: 'Scroll to Top' }, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Home, linux: { primary: KeyMod.Shift | KeyCode.Home }, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.altBufferActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.altBufferActive.negate()), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => activeInstance.scrollToTop() + run: (xterm) => xterm.scrollToTop() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.ClearSelection, title: { value: localize('workbench.action.terminal.clearSelection', "Clear Selection"), original: 'Clear Selection' }, keybinding: { primary: KeyCode.Escape, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.textSelected, TerminalContextKeys.notFindVisible), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.textSelected, TerminalContextKeys.notFindVisible), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - if (activeInstance.hasSelection()) { - activeInstance.clearSelection(); + run: (xterm) => { + if (xterm.hasSelection()) { + xterm.clearSelection(); } } }); @@ -879,33 +906,28 @@ export function registerTerminalActions() { } }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.SelectToPreviousLine, title: { value: localize('workbench.action.terminal.selectToPreviousLine', "Select To Previous Line"), original: 'Select To Previous Line' }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: async (activeInstance) => { - activeInstance.xterm?.markTracker.selectToPreviousLine(); - activeInstance.focus(); + run: async (xterm, _, instance) => { + xterm.markTracker.selectToPreviousLine(); + // prefer to call focus on the TerminalInstance for additional accessibility triggers + (instance || xterm).focus(); } }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.SelectToNextLine, title: { value: localize('workbench.action.terminal.selectToNextLine', "Select To Next Line"), original: 'Select To Next Line' }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: async (activeInstance) => { - activeInstance.xterm?.markTracker.selectToNextLine(); - activeInstance.focus(); + run: async (xterm, _, instance) => { + xterm.markTracker.selectToNextLine(); + // prefer to call focus on the TerminalInstance for additional accessibility triggers + (instance || xterm).focus(); } }); - registerTerminalAction({ - id: TerminalCommandId.ToggleEscapeSequenceLogging, - title: { value: localize('workbench.action.terminal.toggleEscapeSequenceLogging', "Toggle Escape Sequence Logging"), original: 'Toggle Escape Sequence Logging' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (c) => c.service.toggleEscapeSequenceLogging() - }); - registerTerminalAction({ id: TerminalCommandId.SendSequence, title: terminalStrings.sendSequence, @@ -1152,7 +1174,7 @@ export function registerTerminalActions() { } }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.SelectAll, title: { value: localize('workbench.action.terminal.selectAll', "Select All"), original: 'Select All' }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -1165,9 +1187,9 @@ export function registerTerminalActions() { // makes it easier for users to see how it works though. mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyA }, weight: KeybindingWeight.WorkbenchContrib, - when: TerminalContextKeys.focus + when: TerminalContextKeys.focusInAny }], - run: (activeInstance) => activeInstance.selectAll() + run: (xterm) => xterm.selectAll() }); registerTerminalAction({ @@ -1455,42 +1477,48 @@ export function registerTerminalActions() { // Some commands depend on platform features if (BrowserFeatures.clipboard.writeText) { - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.CopySelection, title: { value: localize('workbench.action.terminal.copySelection', "Copy Selection"), original: 'Copy Selection' }, // TODO: Why is copy still showing up when text isn't selected? - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected), + precondition: ContextKeyExpr.or(TerminalContextKeys.textSelectedInFocused, ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected)), keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC, mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyC }, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(TerminalContextKeys.textSelected, TerminalContextKeys.focus) + when: ContextKeyExpr.or( + ContextKeyExpr.and(TerminalContextKeys.textSelected, TerminalContextKeys.focus), + TerminalContextKeys.textSelectedInFocused, + ) }], run: (activeInstance) => activeInstance.copySelection() }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.CopyAndClearSelection, title: { value: localize('workbench.action.terminal.copyAndClearSelection', "Copy and Clear Selection"), original: 'Copy and Clear Selection' }, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected), + precondition: ContextKeyExpr.or(TerminalContextKeys.textSelectedInFocused, ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected)), keybinding: [{ win: { primary: KeyMod.CtrlCmd | KeyCode.KeyC }, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(TerminalContextKeys.textSelected, TerminalContextKeys.focus) + when: ContextKeyExpr.or( + ContextKeyExpr.and(TerminalContextKeys.textSelected, TerminalContextKeys.focus), + TerminalContextKeys.textSelectedInFocused, + ) }], - run: async (activeInstance) => { - await activeInstance.copySelection(); - activeInstance.clearSelection(); + run: async (xterm) => { + await xterm.copySelection(); + xterm.clearSelection(); } }); - registerActiveInstanceAction({ + registerActiveXtermAction({ id: TerminalCommandId.CopySelectionAsHtml, title: { value: localize('workbench.action.terminal.copySelectionAsHtml', "Copy Selection as HTML"), original: 'Copy Selection as HTML' }, f1: true, category, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected), - run: (activeInstance) => activeInstance.copySelection(true) + precondition: ContextKeyExpr.or(TerminalContextKeys.textSelectedInFocused, ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected)), + run: (xterm) => xterm.copySelection(true) }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 9ddc5d62403..eb04b5e2667 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -103,7 +103,7 @@ export class TerminalEditor extends EditorPane { // eslint-disable-next-line @typescript-eslint/naming-convention protected createEditor(parent: HTMLElement): void { this._editorInstanceElement = parent; - this._overflowGuardElement = dom.$('.terminal-overflow-guard'); + this._overflowGuardElement = dom.$('.terminal-overflow-guard.terminal-editor'); this._editorInstanceElement.appendChild(this._overflowGuardElement); this._registerListeners(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index f7826508e51..ad4db231ba1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -17,25 +17,28 @@ import { ErrorNoTelemetry, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ISeparator, template } from 'vs/base/common/labels'; -import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; -import { isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; +import { OS, OperatingSystem, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { TabFocus, TabFocusContext } from 'vs/editor/browser/config/tabFocus'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; +import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -45,18 +48,24 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IMarkProperties, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; +import { IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { deserializeEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariableShared'; import { IProcessDataEvent, IProcessPropertyMap, IReconnectionProperties, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, ShellIntegrationStatus, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; +import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks'; import { IRequestAddInstanceToGroupEvent, ITerminalContribution, ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; +import { TerminalExtensionsRegistry } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { getColorClass, getColorStyleElement, getStandardColors } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { showRunRecentQuickPick } from 'vs/workbench/contrib/terminal/browser/terminalRunRecentQuickPick'; @@ -64,31 +73,21 @@ import { ITerminalStatusList, TerminalStatus, TerminalStatusList } from 'vs/work import { getTerminalResourcesFromDragEvent, getTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { LineDataEventAddon } from 'vs/workbench/contrib/terminal/browser/xterm/lineDataEventAddon'; -import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; +import { XtermTerminal, getXtermScaledDimensions } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { deserializeEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariableShared'; import { getCommandHistory, getDirectoryHistory } from 'vs/workbench/contrib/terminal/common/history'; -import { DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalProcessManager, ITerminalProfileResolverService, ProcessState, TerminalCommandId, TERMINAL_CREATION_COMMANDS, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalProcessManager, ITerminalProfileResolverService, ProcessState, TERMINAL_CREATION_COMMANDS, TERMINAL_VIEW_ID, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { getWorkspaceForTerminal, preparePathForShell } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import type { IMarker, Terminal as XTermTerminal } from 'xterm'; -import { IAudioCueService, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; -import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; -import { getWorkspaceForTerminal, preparePathForShell } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; -import { IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; -import { TERMINAL_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { TerminalExtensionsRegistry } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import type { IMarker, Terminal as XTermTerminal } from 'xterm'; const enum Constants { /** @@ -105,19 +104,6 @@ const enum Constants { } let xtermConstructor: Promise | undefined; -function getXtermConstructor(keybinding?: ResolvedKeybinding): Promise { - if (xtermConstructor) { - return xtermConstructor; - } - xtermConstructor = Promises.withAsyncBody(async (resolve) => { - const Terminal = (await import('xterm')).Terminal; - // Localize strings - Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); - Terminal.strings.tooMuchOutput = keybinding ? nls.localize('terminal.integrated.useAccessibleBuffer', 'Use the accessible buffer {0} to manually review output', keybinding.getLabel()) : nls.localize('terminal.integrated.useAccessibleBufferNoKb', 'Use the Terminal: Focus Accessible Buffer command to manually review output'); - resolve(Terminal); - }); - return xtermConstructor; -} interface ICanvasDimensions { width: number; @@ -642,28 +628,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } const font = this.xterm ? this.xterm.getFont() : this._configHelper.getFont(); - if (!font.charWidth || !font.charHeight) { + const newRC = getXtermScaledDimensions(font, dimension.width, dimension.height); + if (!newRC) { this._setLastKnownColsAndRows(); return null; } - // Because xterm.js converts from CSS pixels to actual pixels through - // the use of canvas, window.devicePixelRatio needs to be used here in - // order to be precise. font.charWidth/charHeight alone as insufficient - // when window.devicePixelRatio changes. - const scaledWidthAvailable = dimension.width * window.devicePixelRatio; - - const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing; - const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); - - const scaledHeightAvailable = dimension.height * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio); - const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight); - const newRows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1); - - if (this._cols !== newCols || this._rows !== newRows) { - this._cols = newCols; - this._rows = newRows; + if (this._cols !== newRC.cols || this._rows !== newRC.rows) { + this._cols = newRC.cols; + this._rows = newRC.rows; this._fireMaximumDimensionsChanged(); } @@ -704,11 +677,26 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; } get shouldPersist(): boolean { return this._processManager.shouldPersist && !this.shellLaunchConfig.isTransient && (!this.reconnectionProperties || this._configurationService.getValue(TaskSettingId.Reconnection) === true); } + public static getXtermConstructor(keybindingService: IKeybindingService, contextKeyService: IContextKeyService) { + const keybinding = keybindingService.lookupKeybinding(TerminalCommandId.FocusAccessibleBuffer, contextKeyService); + if (xtermConstructor) { + return xtermConstructor; + } + xtermConstructor = Promises.withAsyncBody(async (resolve) => { + const Terminal = (await import('xterm')).Terminal; + // Localize strings + Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); + Terminal.strings.tooMuchOutput = keybinding ? nls.localize('terminal.integrated.useAccessibleBuffer', 'Use the accessible buffer {0} to manually review output', keybinding.getLabel()) : nls.localize('terminal.integrated.useAccessibleBufferNoKb', 'Use the Terminal: Focus Accessible Buffer command to manually review output'); + resolve(Terminal); + }); + return xtermConstructor; + } + /** * Create xterm.js instance and attach data listeners. */ protected async _createXterm(): Promise { - const Terminal = await getXtermConstructor(this._keybindingService.lookupKeybinding(TerminalCommandId.FocusAccessibleBuffer, this._contextKeyService)); + const Terminal = await TerminalInstance.getXtermConstructor(this._keybindingService, this._contextKeyService); if (this._isDisposed) { throw new ErrorNoTelemetry('Terminal disposed of during xterm.js creation'); } @@ -1072,25 +1060,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise { const xterm = await this._xtermReadyPromise; - if (this.hasSelection() || (asHtml && command)) { - if (asHtml) { - const textAsHtml = await xterm.getSelectionAsHtml(command); - function listener(e: any) { - if (!e.clipboardData.types.includes('text/plain')) { - e.clipboardData.setData('text/plain', command?.getOutput() ?? ''); - } - e.clipboardData.setData('text/html', textAsHtml); - e.preventDefault(); - } - document.addEventListener('copy', listener); - document.execCommand('copy'); - document.removeEventListener('copy', listener); - } else { - await this._clipboardService.writeText(xterm.raw.getSelection()); - } - } else { - this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy')); - } + await xterm.copySelection(asHtml, command); } get selection(): string | undefined { @@ -1101,12 +1071,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this.xterm?.raw.clearSelection(); } - selectAll(): void { - // Focus here to ensure the terminal context key is set - this.xterm?.raw.focus(); - this.xterm?.raw.selectAll(); - } - private _refreshAltBufferContextKey() { this._terminalAltBufferActiveContextKey.set(!!(this.xterm && this.xterm.raw.buffer.active === this.xterm.raw.buffer.alternate)); } @@ -1738,11 +1702,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); } - async setEscapeSequenceLogging(enable: boolean): Promise { - const xterm = await this._xtermReadyPromise; - xterm.raw.options.logLevel = enable ? 'debug' : 'info'; - } - @debounce(1000) relaunch(): void { this.reuseTerminal(this._shellLaunchConfig, true); @@ -2146,12 +2105,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this.statusList.add(info.getStatus({ workspaceFolder })); } - async toggleEscapeSequenceLogging(): Promise { - const xterm = await this._xtermReadyPromise; - xterm.raw.options.logLevel = xterm.raw.options.logLevel === 'debug' ? 'info' : 'debug'; - return xterm.raw.options.logLevel === 'debug'; - } - async getInitialCwd(): Promise { if (!this._initialCwd) { this._initialCwd = this._processManager.initialCwd; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index b1635689574..5701de1746e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -266,7 +266,7 @@ export class TerminalProfileQuickpick { } const argsString = profile.args.map(e => { if (e.includes(' ')) { - return `"${e.replace(/"/g, '\\"')}"`; + return `"${e.replace(/"/g, '\\"')}"`; // CodeQL [SM02383] js/incomplete-sanitization This is only used as a label on the UI so this isn't a problem } return e; }).join(' '); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 4a2e36cceca..b0d47847513 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -30,7 +30,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { VirtualWorkspaceContext } from 'vs/workbench/common/contextkeys'; import { IEditableData, IViewsService } from 'vs/workbench/common/views'; -import { ICreateTerminalOptions, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ICreateTerminalOptions, IDetachedXTermOptions, IDetachedXtermTerminal, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, IXtermTerminal, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { getCwdForSplit } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; @@ -47,17 +47,20 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILifecycleService, ShutdownReason, StartupKind, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; +import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; export class TerminalService implements ITerminalService { declare _serviceBrand: undefined; private _hostActiveTerminals: Map = new Map(); + private _detachedXterms = new Set(); private _terminalEditorActive: IContextKey; private readonly _terminalShellTypeContextKey: IContextKey; - private _escapeSequenceLoggingEnabled: boolean = false; - private _isShuttingDown: boolean = false; private _backgroundedTerminalInstances: ITerminalInstance[] = []; private _backgroundedTerminalDisposables: Map = new Map(); @@ -80,6 +83,9 @@ export class TerminalService implements ITerminalService { get instances(): ITerminalInstance[] { return this._terminalGroupService.instances.concat(this._terminalEditorService.instances); } + get detachedXterms(): Iterable { + return this._detachedXterms; + } private _reconnectedTerminals: Map = new Map(); getReconnectedTerminals(reconnectionOwner: string): ITerminalInstance[] | undefined { @@ -163,7 +169,8 @@ export class TerminalService implements ITerminalService { @IExtensionService private readonly _extensionService: IExtensionService, @INotificationService private readonly _notificationService: INotificationService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, - @ICommandService private readonly _commandService: ICommandService + @ICommandService private readonly _commandService: ICommandService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper); // the below avoids having to poll routinely. @@ -174,7 +181,6 @@ export class TerminalService implements ITerminalService { this._forwardInstanceHostEvents(this._terminalEditorService); this._terminalGroupService.onDidChangeActiveGroup(this._onDidChangeActiveGroup.fire, this._onDidChangeActiveGroup); this._terminalInstanceService.onDidCreateInstance(instance => { - instance.setEscapeSequenceLogging(this._escapeSequenceLoggingEnabled); this._initInstanceListeners(instance); this._onDidCreateInstance.fire(instance); }); @@ -468,17 +474,6 @@ export class TerminalService implements ITerminalService { return reconnectCounter; } - async toggleEscapeSequenceLogging(): Promise { - if (this.instances.length === 0) { - return; - } - this._escapeSequenceLoggingEnabled = await this.instances[0].toggleEscapeSequenceLogging(); - for (let i = 1; i < this.instances.length; i++) { - this.instances[i].setEscapeSequenceLogging(this._escapeSequenceLoggingEnabled); - } - await this._toggleDevTools(this._escapeSequenceLoggingEnabled); - } - private _attachProcessLayoutListeners(): void { this.onDidChangeActiveGroup(() => this._saveState()); this.onDidChangeActiveInstance(() => this._saveState()); @@ -612,14 +607,6 @@ export class TerminalService implements ITerminalService { this._nativeDelegate = nativeDelegate; } - private async _toggleDevTools(open?: boolean): Promise { - if (open) { - this._nativeDelegate?.openDevTools(); - } else { - this._nativeDelegate?.toggleDevTools(); - } - } - private _shouldReviveProcesses(reason: ShutdownReason): boolean { if (!this._configHelper.config.enablePersistentSessions) { return false; @@ -971,6 +958,31 @@ export class TerminalService implements ITerminalService { return this._createTerminal(shellLaunchConfig, location, options); } + async createDetachedXterm(options: IDetachedXTermOptions): Promise { + const ctor = await TerminalInstance.getXtermConstructor(this._keybindingService, this._contextKeyService); + const instance = this._instantiationService.createInstance( + XtermTerminal, + ctor, + this._configHelper, + options.cols, + options.rows, + options.colorProvider, + options.capabilities || new TerminalCapabilityStore(), + '', + undefined, + false, + ); + + if (options.readonly) { + instance.raw.attachCustomKeyEventHandler(() => false); + } + + this._detachedXterms.add(instance); + instance.onDidDispose(() => this._detachedXterms.delete(instance)); + + return instance; + } + private async _resolveCwd(shellLaunchConfig: IShellLaunchConfig, splitActiveTerminal: boolean, options?: ICreateTerminalOptions): Promise { const cwd = shellLaunchConfig.cwd; if (!cwd) { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index c6f4d4ea87f..cfb38995a78 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { IBuffer, ITheme, Terminal as RawXtermTerminal } from 'xterm'; +import type { IBuffer, ITheme, Terminal as RawXtermTerminal, LogLevel as XtermLogLevel } from 'xterm'; import type { CanvasAddon as CanvasAddonType } from 'xterm-addon-canvas'; import type { ISearchOptions, SearchAddon as SearchAddonType } from 'xterm-addon-search'; import type { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11'; import type { WebglAddon as WebglAddonType } from 'xterm-addon-webgl'; import type { SerializeAddon as SerializeAddonType } from 'xterm-addon-serialize'; import type { ImageAddon as ImageAddonType } from 'xterm-addon-image'; +import * as dom from 'vs/base/browser/dom'; import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; @@ -18,8 +19,8 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IShellIntegration, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ITerminalFont } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; -import { IMarkTracker, IInternalXtermTerminal, IXtermTerminal, ISuggestController, IXtermColorProvider, XtermTerminalConstants } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ILogService } from 'vs/platform/log/common/log'; +import { IMarkTracker, IInternalXtermTerminal, IXtermTerminal, ISuggestController, IXtermColorProvider, XtermTerminalConstants, IXtermAttachToElementOptions, IDetachedXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ILogger, ILoggerService, LogLevel } from 'vs/platform/log/common/log'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; @@ -35,7 +36,9 @@ import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from ' import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { SuggestAddon } from 'vs/workbench/contrib/terminal/browser/xterm/suggestAddon'; -import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; const enum RenderConstants { /** @@ -108,31 +111,20 @@ function getFullBufferLineAsString(lineIndex: number, buffer: IBuffer): { lineDa * Wraps the xterm object with additional functionality. Interaction with the backing process is out * of the scope of this class. */ -export class XtermTerminal extends DisposableStore implements IXtermTerminal, IInternalXtermTerminal { +export class XtermTerminal extends DisposableStore implements IXtermTerminal, IDetachedXtermTerminal, IInternalXtermTerminal { /** The raw xterm.js instance */ readonly raw: RawXtermTerminal; - - *getBufferReverseIterator(): IterableIterator { - for (let i = this.raw.buffer.active.length; i >= 0; i--) { - const { lineData, lineIndex } = getFullBufferLineAsString(i, this.raw.buffer.active); - if (lineData) { - i = lineIndex; - yield lineData; - } - } - } private _core: IXtermCore; private static _suggestedRendererType: 'canvas' | 'dom' | undefined = undefined; - private _container?: HTMLElement; + private _attached?: { container: HTMLElement; options: IXtermAttachToElementOptions }; // Always on addons private _markNavigationAddon: MarkNavigationAddon; private _shellIntegrationAddon: ShellIntegrationAddon; - private _decorationAddon: DecorationAddon; - private _suggestAddon: SuggestAddon; // Optional addons + private _suggestAddon?: SuggestAddon; private _canvasAddon?: CanvasAddonType; private _searchAddon?: SearchAddonType; private _unicode11Addon?: Unicode11AddonType; @@ -140,8 +132,14 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II private _serializeAddon?: SerializeAddonType; private _imageAddon?: ImageAddonType; + private readonly _logger: ILogger; + private readonly _attachedDisposables = this.add(new DisposableStore()); + private readonly _anyTerminalFocusContextKey: IContextKey; + private readonly _anyFocusedTerminalHasSelection: IContextKey; + private _lastFindResult: { resultIndex: number; resultCount: number } | undefined; get findResult(): { resultIndex: number; resultCount: number } | undefined { return this._lastFindResult; } + get isStdinDisabled(): boolean { return !!this.raw.options.disableStdin; } private readonly _onDidRequestRunCommand = new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>(); @@ -156,6 +154,10 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II readonly onDidChangeFindResults = this._onDidChangeFindResults.event; private readonly _onDidChangeSelection = new Emitter(); readonly onDidChangeSelection = this._onDidChangeSelection.event; + private readonly _onDidChangeFocus = new Emitter(); + readonly onDidChangeFocus = this._onDidChangeFocus.event; + private readonly _onDidDispose = new Emitter(); + readonly onDidDispose = this._onDidDispose.event; get markTracker(): IMarkTracker { return this._markNavigationAddon; } get shellIntegration(): IShellIntegration { return this._shellIntegrationAddon; } @@ -169,6 +171,10 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II return createImageBitmap(canvas); } + public get isFocused() { + return !!this.raw.element?.contains(document.activeElement); + } + /** * @param xtermCtor The xterm.js constructor, this is passed in so it can be fetched lazily * outside of this class such that {@link raw} is not nullable. @@ -181,21 +187,24 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II private readonly _backgroundColorProvider: IXtermColorProvider, private readonly _capabilities: ITerminalCapabilityStore, shellIntegrationNonce: string, - private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey, + private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey | undefined, disableShellIntegrationReporting: boolean, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ILogService private readonly _logService: ILogService, + @ILoggerService private readonly _loggerService: ILoggerService, @INotificationService private readonly _notificationService: INotificationService, @IStorageService private readonly _storageService: IStorageService, @IThemeService private readonly _themeService: IThemeService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IClipboardService private readonly _clipboardService: IClipboardService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; const editorOptions = this._configurationService.getValue('editor'); + this._logger = this._loggerService.createLogger('terminal', { name: localize('terminalLoggerName', 'Terminal') }); this.raw = this.add(new xtermCtor({ allowProposedApi: true, cols, @@ -210,6 +219,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II fontSize: font.fontSize, letterSpacing: font.letterSpacing, lineHeight: font.lineHeight, + logLevel: vscodeToXtermLogLevel(this._logger.getLevel()), + logger: this._logger, minimumContrastRatio: config.minimumContrastRatio, tabStopWidth: config.tabStopWidth, cursorBlink: config.cursorBlinking, @@ -240,9 +251,15 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II })); this.add(this._themeService.onDidColorThemeChange(theme => this._updateTheme(theme))); + this.add(this._logger.onDidChangeLogLevel(e => this.raw.options.logLevel = vscodeToXtermLogLevel(e))); // Refire events - this.add(this.raw.onSelectionChange(() => this._onDidChangeSelection.fire())); + this.add(this.raw.onSelectionChange(() => { + this._onDidChangeSelection.fire(); + if (this.isFocused) { + this._anyFocusedTerminalHasSelection.set(this.raw.hasSelection()); + } + })); // Load addons this._updateUnicodeVersion(); @@ -255,14 +272,39 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, shellIntegrationNonce, disableShellIntegrationReporting, this._telemetryService); this.raw.loadAddon(this._shellIntegrationAddon); + this._anyTerminalFocusContextKey = TerminalContextKeys.focusInAny.bindTo(contextKeyService); + this._anyFocusedTerminalHasSelection = TerminalContextKeys.textSelectedInFocused.bindTo(contextKeyService); + // Load the suggest addon, this should be loaded regardless of the setting as the sequences // may still come in - this._suggestAddon = this._instantiationService.createInstance(SuggestAddon, this._terminalSuggestWidgetVisibleContextKey); - this.raw.loadAddon(this._suggestAddon); - this._suggestAddon.onAcceptedCompletion(async text => { - this._onDidRequestFocus.fire(); - this._onDidRequestSendText.fire(text); - }); + if (this._terminalSuggestWidgetVisibleContextKey) { + this._suggestAddon = this._instantiationService.createInstance(SuggestAddon, this._terminalSuggestWidgetVisibleContextKey); + this.raw.loadAddon(this._suggestAddon); + this._suggestAddon.onAcceptedCompletion(async text => { + this._onDidRequestFocus.fire(); + this._onDidRequestSendText.fire(text); + }); + } + } + + *getBufferReverseIterator(): IterableIterator { + for (let i = this.raw.buffer.active.length; i >= 0; i--) { + const { lineData, lineIndex } = getFullBufferLineAsString(i, this.raw.buffer.active); + if (lineData) { + i = lineIndex; + yield lineData; + } + } + } + + async getContentsAsHtml(): Promise { + if (!this._serializeAddon) { + const Addon = await this._getSerializeAddonConstructor(); + this._serializeAddon = new Addon(); + this.raw.loadAddon(this._serializeAddon); + } + + return this._serializeAddon.serializeAsHTML(); } async getSelectionAsHtml(command?: ITerminalCommand): Promise { @@ -286,22 +328,50 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II return result; } - attachToElement(container: HTMLElement): HTMLElement { - if (!this._container) { + attachToElement(container: HTMLElement, partialOptions?: Partial): HTMLElement { + const options: IXtermAttachToElementOptions = { enableGpu: true, ...partialOptions }; + if (!this._attached) { this.raw.open(container); } + // TODO: Move before open to the DOM renderer doesn't initialize - if (this._shouldLoadWebgl()) { - this._enableWebglRenderer(); - } else if (this._shouldLoadCanvas()) { - this._enableCanvasRenderer(); + if (options.enableGpu) { + if (this._shouldLoadWebgl()) { + this._enableWebglRenderer(); + } else if (this._shouldLoadCanvas()) { + this._enableCanvasRenderer(); + } } - this._suggestAddon.setContainer(container); + if (!this.raw.element || !this.raw.textarea) { + throw new Error('xterm elements not set after open'); + } - this._container = container; + const ad = this._attachedDisposables; + ad.clear(); + ad.add(dom.addDisposableListener(this.raw.textarea, 'focus', () => this._setFocused(true))); + ad.add(dom.addDisposableListener(this.raw.textarea, 'blur', () => this._setFocused(false))); + ad.add(dom.addDisposableListener(this.raw.textarea, 'focusout', () => this._setFocused(false))); + + this._suggestAddon?.setContainer(container); + + this._attached = { container, options }; // Screen must be created at this point as xterm.open is called - return this._container.querySelector('.xterm-screen')!; + return this._attached?.container.querySelector('.xterm-screen')!; + } + + private _setFocused(isFocused: boolean) { + this._onDidChangeFocus.fire(isFocused); + this._anyTerminalFocusContextKey.set(isFocused); + this._anyFocusedTerminalHasSelection.set(isFocused && this.raw.hasSelection()); + } + + write(data: string | Uint8Array): void { + this.raw.write(data); + } + + resize(columns: number, rows: number): void { + this.raw.resize(columns, rows); } updateConfig(): void { @@ -324,14 +394,16 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this.raw.options.wordSeparator = config.wordSeparators; this.raw.options.customGlyphs = config.customGlyphs; this.raw.options.smoothScrollDuration = config.smoothScrolling ? RenderConstants.SmoothScrollDuration : 0; - if (this._shouldLoadWebgl()) { - this._enableWebglRenderer(); - } else { - this._disposeOfWebglRenderer(); - if (this._shouldLoadCanvas()) { - this._enableCanvasRenderer(); + if (this._attached?.options.enableGpu) { + if (this._shouldLoadWebgl()) { + this._enableWebglRenderer(); } else { - this._disposeOfCanvasRenderer(); + this._disposeOfWebglRenderer(); + if (this._shouldLoadCanvas()) { + this._enableCanvasRenderer(); + } else { + this._disposeOfCanvasRenderer(); + } } } this._refreshImageAddon(); @@ -496,6 +568,45 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this._capabilities.get(TerminalCapability.CommandDetection)?.handleCommandStart(); } + hasSelection(): boolean { + return this.raw.hasSelection(); + } + + clearSelection(): void { + this.raw.clearSelection(); + } + + selectAll(): void { + this.raw.focus(); + this.raw.selectAll(); + } + + focus(): void { + this.raw.focus(); + } + + async copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise { + if (this.hasSelection() || (asHtml && command)) { + if (asHtml) { + const textAsHtml = await this.getSelectionAsHtml(command); + function listener(e: any) { + if (!e.clipboardData.types.includes('text/plain')) { + e.clipboardData.setData('text/plain', command?.getOutput() ?? ''); + } + e.clipboardData.setData('text/html', textAsHtml); + e.preventDefault(); + } + document.addEventListener('copy', listener); + document.execCommand('copy'); + document.removeEventListener('copy', listener); + } else { + await this._clipboardService.writeText(this.raw.getSelection()); + } + } else { + this._notificationService.warn(localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy')); + } + } + private _setCursorBlink(blink: boolean): void { if (this.raw.options.cursorBlink !== blink) { this.raw.options.cursorBlink = blink; @@ -525,9 +636,9 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this._disposeOfCanvasRenderer(); try { this.raw.loadAddon(this._webglAddon); - this._logService.trace('Webgl was loaded'); + this._logger.trace('Webgl was loaded'); this._webglAddon.onContextLoss(() => { - this._logService.info(`Webgl lost context, disposing of webgl renderer`); + this._logger.info(`Webgl lost context, disposing of webgl renderer`); this._disposeOfWebglRenderer(); }); // Uncomment to add the texture atlas to the DOM @@ -537,7 +648,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II // } // }, 5000); } catch (e) { - this._logService.warn(`Webgl could not be loaded. Falling back to the canvas renderer type.`, e); + this._logger.warn(`Webgl could not be loaded. Falling back to the canvas renderer type.`, e); const neverMeasureRenderTime = this._storageService.getBoolean(TerminalStorageKeys.NeverMeasureRenderTime, StorageScope.APPLICATION, false); // if it's already set to dom, no need to measure render time if (!neverMeasureRenderTime && this._configHelper.config.gpuAcceleration !== 'off') { @@ -558,9 +669,9 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II this._disposeOfWebglRenderer(); try { this.raw.loadAddon(this._canvasAddon); - this._logService.trace('Canvas renderer was loaded'); + this._logger.trace('Canvas renderer was loaded'); } catch (e) { - this._logService.warn(`Canvas renderer could not be loaded, falling back to dom renderer`, e); + this._logger.warn(`Canvas renderer could not be loaded, falling back to dom renderer`, e); const neverMeasureRenderTime = this._storageService.getBoolean(TerminalStorageKeys.NeverMeasureRenderTime, StorageScope.APPLICATION, false); // if it's already set to dom, no need to measure render time if (!neverMeasureRenderTime && this._configHelper.config.gpuAcceleration !== 'off') { @@ -765,4 +876,44 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II _writeText(data: string): void { this.raw.write(data); } + + public override dispose(): void { + this._anyTerminalFocusContextKey.reset(); + this._anyFocusedTerminalHasSelection.reset(); + this._onDidDispose.fire(); + super.dispose(); + } +} + +export function getXtermScaledDimensions(font: ITerminalFont, width: number, height: number) { + if (!font.charWidth || !font.charHeight) { + return null; + } + + // Because xterm.js converts from CSS pixels to actual pixels through + // the use of canvas, window.devicePixelRatio needs to be used here in + // order to be precise. font.charWidth/charHeight alone as insufficient + // when window.devicePixelRatio changes. + const scaledWidthAvailable = width * window.devicePixelRatio; + + const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing; + const cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); + + const scaledHeightAvailable = height * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio); + const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight); + const rows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1); + + return { rows, cols }; +} + +function vscodeToXtermLogLevel(logLevel: LogLevel): XtermLogLevel { + switch (logLevel) { + case LogLevel.Trace: + case LogLevel.Debug: return 'debug'; + case LogLevel.Info: return 'info'; + case LogLevel.Warning: return 'warn'; + case LogLevel.Error: return 'error'; + default: return 'off'; + } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 8503f5c471f..c0355a1f873 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -576,7 +576,6 @@ export const enum TerminalCommandId { SelectToNextCommand = 'workbench.action.terminal.selectToNextCommand', SelectToPreviousLine = 'workbench.action.terminal.selectToPreviousLine', SelectToNextLine = 'workbench.action.terminal.selectToNextLine', - ToggleEscapeSequenceLogging = 'toggleEscapeSequenceLogging', SendSequence = 'workbench.action.terminal.sendSequence', ToggleFindRegex = 'workbench.action.terminal.toggleFindRegex', ToggleFindWholeWord = 'workbench.action.terminal.toggleFindWholeWord', diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index a3eb8693d01..1d480dbd649 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -14,6 +14,7 @@ export const enum TerminalContextKeyStrings { HasFixedWidth = 'terminalHasFixedWidth', ProcessSupported = 'terminalProcessSupported', Focus = 'terminalFocus', + FocusInAny = 'terminalFocusInAny', AccessibleBufferFocus = 'terminalAccessibleBufferFocus', EditorFocus = 'terminalEditorFocus', TabsFocus = 'terminalTabsFocus', @@ -26,6 +27,7 @@ export const enum TerminalContextKeyStrings { A11yTreeFocus = 'terminalA11yTreeFocus', ViewShowing = 'terminalViewShowing', TextSelected = 'terminalTextSelected', + TextSelectedInFocused = 'terminalTextSelectedInFocused', FindVisible = 'terminalFindVisible', FindInputFocused = 'terminalFindInputFocused', FindFocused = 'terminalFindFocused', @@ -43,6 +45,9 @@ export namespace TerminalContextKeys { /** Whether the terminal is focused. */ export const focus = new RawContextKey(TerminalContextKeyStrings.Focus, false, localize('terminalFocusContextKey', "Whether the terminal is focused.")); + /** Whether any terminal is focused, including detached terminals used in other UI. */ + export const focusInAny = new RawContextKey(TerminalContextKeyStrings.FocusInAny, false, localize('terminalFocusInAnyContextKey', "Whether any terminal is focused, including detached terminals used in other UI.")); + /** Whether the accessible buffer is focused. */ export const accessibleBufferFocus = new RawContextKey(TerminalContextKeyStrings.AccessibleBufferFocus, false, localize('terminalAccessibleBufferFocusContextKey', "Whether the terminal accessible buffer is focused.")); @@ -94,6 +99,9 @@ export namespace TerminalContextKeys { /** Whether text is selected in the active terminal. */ export const textSelected = new RawContextKey(TerminalContextKeyStrings.TextSelected, false, localize('terminalTextSelectedContextKey', "Whether text is selected in the active terminal.")); + /** Whether text is selected in a focused terminal. `textSelected` counts text selected in an active in a terminal view or an editor, where `textSelectedInFocused` simply counts text in an element with DOM focus. */ + export const textSelectedInFocused = new RawContextKey(TerminalContextKeyStrings.TextSelectedInFocused, false, localize('terminalTextSelectedInFocusedContextKey', "Whether text is selected in a focused terminal.")); + /** Whether text is NOT selected in the active terminal. */ export const notTextSelected = textSelected.toNegated(); diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts index 19dbb559546..2b541d9fbbd 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts @@ -29,9 +29,7 @@ export class TerminalNativeContribution extends Disposable implements IWorkbench this._register(nativeHostService.onDidResumeOS(() => this._onOsResume())); this._terminalService.setNativeDelegate({ - getWindowCount: () => nativeHostService.getWindowCount(), - openDevTools: () => nativeHostService.openDevTools(), - toggleDevTools: () => nativeHostService.toggleDevTools() + getWindowCount: () => nativeHostService.getWindowCount() }); const connection = remoteAgentService.getConnection(); diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts index a9c5ff1af01..f6ee3248f66 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts @@ -19,9 +19,9 @@ import { Emitter } from 'vs/base/common/event'; import { TERMINAL_BACKGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, TERMINAL_SELECTION_FOREGROUND_COLOR, TERMINAL_INACTIVE_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { WebglAddon } from 'xterm-addon-webgl'; -import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { ILoggerService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestLoggerService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { isSafari } from 'vs/base/browser/browser'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -30,6 +30,7 @@ import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestSer import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { Color, RGBA } from 'vs/base/common/color'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; class TestWebglAddon implements WebglAddon { static shouldThrow = false; @@ -111,12 +112,13 @@ suite('XtermTerminal', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(IConfigurationService, configurationService); - instantiationService.stub(ILogService, new NullLogService()); + instantiationService.stub(ILoggerService, new TestLoggerService()); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IThemeService, themeService); instantiationService.stub(IViewDescriptorService, viewDescriptorService); instantiationService.stub(IContextMenuService, instantiationService.createInstance(ContextMenuService)); instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); configHelper = instantiationService.createInstance(TerminalConfigHelper); xterm = instantiationService.createInstance(TestXtermTerminal, Terminal, configHelper, 80, 30, { getBackgroundColor: () => undefined }, new TerminalCapabilityStore(), '', new MockContextKeyService().createKey('', true)!, true); @@ -251,7 +253,7 @@ suite('XtermTerminal', () => { // Open xterm as otherwise the webgl addon won't activate const container = document.createElement('div'); - xterm.raw.open(container); + xterm.attachToElement(container); // Auto should activate the webgl addon await configurationService.setUserConfiguration('terminal', { integrated: { ...defaultTerminalConfig, gpuAcceleration: 'auto' } }); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts index 013caf6e2fb..d1316db9bee 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts @@ -66,7 +66,7 @@ export abstract class TerminalAccessibleWidget extends DisposableStore { }; const font = _xterm.getFont(); const editorOptions: IEditorConstructionOptions = { - ...getSimpleEditorOptions(), + ...getSimpleEditorOptions(this._configurationService), lineDecorationsWidth: 6, dragAndDrop: true, cursorWidth: 1, @@ -80,8 +80,6 @@ export abstract class TerminalAccessibleWidget extends DisposableStore { quickSuggestions: false, renderWhitespace: 'none', dropIntoEditor: { enabled: true }, - accessibilitySupport: this._configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), - cursorBlinking: this._configurationService.getValue('terminal.integrated.cursorBlinking'), readOnly: true }; this._editorWidget = this.add(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions)); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 8ee82c2813d..d55f7aa0123 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -7,11 +7,12 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService, NullLogService } from 'vs/platform/log/common/log'; import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -23,6 +24,7 @@ import { ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/ter import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLoggerService } from 'vs/workbench/test/common/workbenchTestServices'; import { Terminal } from 'xterm'; const defaultTerminalConfig: Partial = { @@ -54,8 +56,10 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IConfigurationService, configurationService); instantiationService.stub(IThemeService, themeService); instantiationService.stub(ILogService, new NullLogService()); + instantiationService.stub(ILoggerService, new TestLoggerService()); instantiationService.stub(IContextMenuService, instantiationService.createInstance(ContextMenuService)); instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); configHelper = instantiationService.createInstance(TerminalConfigHelper); capabilities = new TerminalCapabilityStore(); if (!isWindows) { diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index e42b0ddbdfe..11ae1f8784f 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -186,8 +186,8 @@ border-bottom-width: 2px; } -.monaco-editor .zone-widget.test-output-peek .test-output-peek-message-container, -.monaco-editor .zone-widget.test-output-peek .test-output-peek-tree { +.test-output-peek-message-container, +.test-output-peek-tree { height: 100%; } diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index bd2c7faf1fc..1e490d507e1 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -31,7 +31,7 @@ import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewPaneContainer } fro import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; import * as icons from 'vs/workbench/contrib/testing/browser/icons'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; -import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService'; +import { TestResultsView } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { TestingConfigKeys, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; import { TestCommandId, TestExplorerViewMode, TestExplorerViewSorting, Testing, testConfigurationGroupNames } from 'vs/workbench/contrib/testing/common/constants'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; @@ -840,30 +840,9 @@ export class ShowMostRecentOutputAction extends Action2 { } public async run(accessor: ServicesAccessor) { - const quickInputService = accessor.get(IQuickInputService); - const terminalOutputService = accessor.get(ITestingOutputTerminalService); - const result = accessor.get(ITestResultService).results[0]; - - if (!result.tasks.length) { - return; - } - - let index = 0; - if (result.tasks.length > 1) { - const picked = await quickInputService.pick( - result.tasks.map((t, i) => ({ label: t.name || localize('testing.pickTaskUnnamed', "Run #{0}", i), index: i })), - { placeHolder: localize('testing.pickTask', "Pick a run to show output for") } - ); - - if (!picked) { - return; - } - - index = picked.index; - } - - - terminalOutputService.open(result, index); + const viewService = accessor.get(IViewsService); + const testView = await viewService.openView(Testing.ResultsViewId, true); + testView?.showLatestRun(); } } diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 5569b1fc9f6..73030bcd305 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -16,35 +16,34 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as ViewContainerExtensions, IViewContainersRegistry, IViewsRegistry, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IViewContainersRegistry, IViewsRegistry, IViewsService, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; import { REVEAL_IN_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; import { testingResultsIcon, testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons'; -import { TestingDecorations, TestingDecorationService } from 'vs/workbench/contrib/testing/browser/testingDecorations'; +import { TestingDecorationService, TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; import { CloseTestPeek, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; -import { ITestingOutputTerminalService, TestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService'; import { ITestingProgressUiService, TestingProgressTrigger, TestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService'; import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { testingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; import { TestCommandId, Testing } from 'vs/workbench/contrib/testing/common/constants'; -import { ITestItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes'; import { ITestExplorerFilterState, TestExplorerFilterState } from 'vs/workbench/contrib/testing/common/testExplorerFilterState'; import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; -import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider'; -import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; -import { ITestingDecorationsService } from 'vs/workbench/contrib/testing/common/testingDecorations'; -import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { ITestProfileService, TestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; import { ITestResultService, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestResultStorage, TestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl'; +import { ITestItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes'; +import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider'; +import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; +import { ITestingContinuousRunService, TestingContinuousRunService } from 'vs/workbench/contrib/testing/common/testingContinuousRunService'; +import { ITestingDecorationsService } from 'vs/workbench/contrib/testing/common/testingDecorations'; +import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { allTestActions, discoverAndRunTests } from './testExplorerActions'; import './testingConfigurationUi'; -import { ITestingContinuousRunService, TestingContinuousRunService } from 'vs/workbench/contrib/testing/common/testingContinuousRunService'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; registerSingleton(ITestService, TestService, InstantiationType.Delayed); registerSingleton(ITestResultStorage, TestResultStorage, InstantiationType.Delayed); @@ -52,7 +51,6 @@ registerSingleton(ITestProfileService, TestProfileService, InstantiationType.Del registerSingleton(ITestingContinuousRunService, TestingContinuousRunService, InstantiationType.Delayed); registerSingleton(ITestResultService, TestResultService, InstantiationType.Delayed); registerSingleton(ITestExplorerFilterState, TestExplorerFilterState, InstantiationType.Delayed); -registerSingleton(ITestingOutputTerminalService, TestingOutputTerminalService, InstantiationType.Delayed); registerSingleton(ITestingPeekOpener, TestingPeekOpener, InstantiationType.Delayed); registerSingleton(ITestingProgressUiService, TestingProgressUiService, InstantiationType.Delayed); registerSingleton(ITestingDecorationsService, TestingDecorationService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index e12d5881f36..aa47d25f78b 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -44,7 +44,7 @@ import { DefaultGutterClickAction, TestingConfigKeys, getTestingConfiguration } import { Testing, labelForTestInState } from 'vs/workbench/contrib/testing/common/constants'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; -import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; +import { ITestResult, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService, getContextForTestItem, testsInFile } from 'vs/workbench/contrib/testing/common/testService'; import { IRichLocation, ITestMessage, ITestRunProfile, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes'; @@ -318,47 +318,11 @@ export class TestingDecorationService extends Disposable implements ITestingDeco } } - const lastResult = this.results.results[0]; - if (this.testService.showInlineOutput.value && lastResult instanceof LiveTestResult) { - for (const task of lastResult.tasks) { - for (const m of task.otherMessages) { - if (!this.invalidatedMessages.has(m) && m.location?.uri.toString() === uriStr) { - const decoration = lastDecorations.getMessage(m) || this.instantiationService.createInstance(TestMessageDecoration, m, undefined, model); - newDecorations.addMessage(decoration); - } - } - } - - const messageLines = new Map(); - for (const test of lastResult.tests) { - for (let taskId = 0; taskId < test.tasks.length; taskId++) { - const state = test.tasks[taskId]; - for (let i = 0; i < state.messages.length; i++) { - const m = state.messages[i]; - if (this.invalidatedMessages.has(m) || m.location?.uri.toString() !== uriStr) { - continue; - } - - // Only add one message per line number. Overlapping messages - // don't appear well, and the peek will show all of them (#134129) - const line = m.location.range.startLineNumber; - if (messageLines.has(line)) { - newDecorations.removeMessage(messageLines.get(line)!); - } - - const decoration = lastDecorations.getMessage(m) || this.instantiationService.createInstance(TestMessageDecoration, m, buildTestUri({ - type: TestUriType.ResultActualOutput, - messageIndex: i, - taskIndex: taskId, - resultId: lastResult.id, - testExtId: test.item.extId, - }), model); - - newDecorations.addMessage(decoration); - messageLines.set(line, decoration.testMessage); - } - } - } + const messageLines = new Set(); + if (getTestingConfiguration(this.configurationService, TestingConfigKeys.ShowAllMessages)) { + this.results.results.forEach(lastResult => this.applyDecorationsFromResult(lastResult, messageLines, uriStr, lastDecorations, model, newDecorations)); + } else { + this.applyDecorationsFromResult(this.results.results[0], messageLines, uriStr, lastDecorations, model, newDecorations); } const saveFromRemoval = new Set(); @@ -387,6 +351,47 @@ export class TestingDecorationService extends Disposable implements ITestingDeco return newDecorations || lastDecorations; } + + private applyDecorationsFromResult(lastResult: ITestResult, messageLines: Set, uriStr: string, lastDecorations: CachedDecorations, model: ITextModel, newDecorations: CachedDecorations) { + if (this.testService.showInlineOutput.value && lastResult instanceof LiveTestResult) { + for (const task of lastResult.tasks) { + for (const m of task.otherMessages) { + if (!this.invalidatedMessages.has(m) && m.location?.uri.toString() === uriStr) { + const decoration = lastDecorations.getMessage(m) || this.instantiationService.createInstance(TestMessageDecoration, m, undefined, model); + newDecorations.addMessage(decoration); + } + } + } + + for (const test of lastResult.tests) { + for (let taskId = 0; taskId < test.tasks.length; taskId++) { + const state = test.tasks[taskId]; + for (let i = 0; i < state.messages.length; i++) { + const m = state.messages[i]; + if (this.invalidatedMessages.has(m) || m.location?.uri.toString() !== uriStr) { + continue; + } + + // Only add one message per line number. Overlapping messages + // don't appear well, and the peek will show all of them (#134129) + const line = m.location.range.startLineNumber; + if (!messageLines.has(line)) { + const decoration = lastDecorations.getMessage(m) || this.instantiationService.createInstance(TestMessageDecoration, m, buildTestUri({ + type: TestUriType.ResultActualOutput, + messageIndex: i, + taskIndex: taskId, + resultId: lastResult.id, + testExtId: test.item.extId, + }), model); + + newDecorations.addMessage(decoration); + messageLines.add(line); + } + } + } + } + } + } } export class TestingDecorations extends Disposable implements IEditorContribution { diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 3eec54c4a97..4076b424ae6 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -34,7 +34,6 @@ import 'vs/css!./testingOutputPeek'; import { ICodeEditor, IDiffEditorConstructionOptions, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -44,7 +43,7 @@ import { IEditor, IEditorContribution, ScrollType } from 'vs/editor/common/edito import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; -import { IPeekViewService, PeekViewWidget, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView'; +import { IPeekViewService, PeekViewWidget, peekViewResultsBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView'; import { localize } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -62,14 +61,20 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IDetachedXtermTerminal, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { getXtermScaledDimensions } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; +import { TERMINAL_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { flatTestItemDelimiter } from 'vs/workbench/contrib/testing/browser/explorerProjections/display'; import { getTestItemContextOverlay } from 'vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay'; import * as icons from 'vs/workbench/contrib/testing/browser/icons'; -import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService'; import { testingPeekBorder, testingPeekHeaderBackground } from 'vs/workbench/contrib/testing/browser/theme'; import { AutoOpenPeekViewWhen, TestingConfigKeys, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; @@ -719,11 +724,12 @@ class TestResultsViewContent extends Disposable { this.splitView = new SplitView(containerElement, { orientation: Orientation.HORIZONTAL }); const { historyVisible, showRevealLocationOnMessages } = this.options; + const isInPeekView = this.editor !== undefined; const messageContainer = dom.append(containerElement, dom.$('.test-output-peek-message-container')); this.contentProviders = [ this._register(this.instantiationService.createInstance(DiffContentProvider, this.editor, messageContainer)), this._register(this.instantiationService.createInstance(MarkdownTestMessagePeek, messageContainer)), - this._register(this.instantiationService.createInstance(PlainTextMessagePeek, this.editor, messageContainer)), + this._register(this.instantiationService.createInstance(PlainTextMessagePeek, messageContainer, isInPeekView)), ]; const treeContainer = dom.append(containerElement, dom.$('.test-output-peek-tree')); @@ -938,6 +944,18 @@ export class TestResultsView extends ViewPane { return this.content.current; } + public showLatestRun(preserveFocus = false) { + const result = this.resultService.results.find(r => r.tasks.length); + if (!result) { + return; + } + + this.content.reveal({ + preserveFocus, + subject: new TaskSubject(result.id, 0), + }); + } + protected override renderBody(container: HTMLElement): void { super.renderBody(container); this.content.fillBody(container); @@ -1139,65 +1157,155 @@ class MarkdownTestMessagePeek extends Disposable implements IPeekOutputRenderer } } + class PlainTextMessagePeek extends Disposable implements IPeekOutputRenderer { - private readonly widget = this._register(new MutableDisposable()); - private readonly model = this._register(new MutableDisposable()); - private dimension?: dom.IDimension; + private dimensions?: dom.IDimension; + private readonly terminalCwd = this._register(new MutableObservableValue('')); + + /** Active terminal instance. */ + private readonly terminal = this._register(new MutableDisposable()); + /** Listener for streaming result data */ + private readonly outputDataListener = this._register(new MutableDisposable()); constructor( - private readonly editor: ICodeEditor | undefined, private readonly container: HTMLElement, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextModelService private readonly modelService: ITextModelService, + private readonly isInPeekView: boolean, + @ITestResultService private readonly resultService: ITestResultService, + @ITerminalService private readonly terminalService: ITerminalService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IWorkspaceContextService private readonly workspaceContext: IWorkspaceContextService, ) { super(); } + private async makeTerminal() { + const prev = this.terminal.value; + if (prev) { + prev.clearBuffer(); + prev.clearSearchDecorations(); + // clearBuffer tries to retain the prompt line, but this doesn't exist for tests. + // So clear the screen (J) and move to home (H) to ensure previous data is cleaned up. + prev.write(`\x1b[2J\x1b[0;0H`); + return prev; + } + + const capabilities = new TerminalCapabilityStore(); + const cwd = this.terminalCwd; + capabilities.add(TerminalCapability.CwdDetection, { + type: TerminalCapability.CwdDetection, + get cwds() { return [cwd.value]; }, + onDidChangeCwd: cwd.onDidChange, + getCwd: () => cwd.value, + updateCwd: () => { }, + }); + + return this.terminal.value = await this.terminalService.createDetachedXterm({ + rows: 10, + cols: 80, + readonly: true, + capabilities, + colorProvider: { + getBackgroundColor: theme => { + const terminalBackground = theme.getColor(TERMINAL_BACKGROUND_COLOR); + if (terminalBackground) { + return terminalBackground; + } + if (this.isInPeekView) { + return theme.getColor(peekViewResultsBackground); + } + const location = this.viewDescriptorService.getViewLocationById(Testing.ResultsViewId); + return location === ViewContainerLocation.Panel + ? theme.getColor(PANEL_BACKGROUND) + : theme.getColor(SIDE_BAR_BACKGROUND); + }, + } + }); + } + public async update(subject: InspectSubject) { - let uri: URI; + this.outputDataListener.clear(); + if (subject instanceof MessageSubject) { const message = subject.messages[subject.messageIndex]; if (isDiffable(message) || typeof message.message !== 'string') { return this.clear(); } - uri = subject.messageUri; + + this.updateCwd(subject.test.uri); + const terminal = await this.makeTerminal(); + terminal.write(message.message); + this.layoutTerminal(terminal); + this.attachTerminalToDom(terminal); } else { - uri = subject.outputUri; - } - - - const modelRef = this.model.value = await this.modelService.createModelReference(uri); - if (!this.widget.value) { - this.widget.value = this.editor ? this.instantiationService.createInstance( - EmbeddedCodeEditorWidget, - this.container, - commonEditorOptions, - {}, - this.editor, - ) : this.instantiationService.createInstance( - CodeEditorWidget, - this.container, - commonEditorOptions, - { isSimpleWidget: true } - ); - - if (this.dimension) { - this.widget.value.layout(this.dimension); + const result = this.resultService.getResult(subject.resultId); + const task = result?.tasks[subject.taskIndex]; + if (!task) { + return this.clear(); } - } - this.widget.value.setModel(modelRef.object.textEditorModel); - this.widget.value.updateOptions(commonEditorOptions); + // Update the cwd and use the first test to try to hint at the correct cwd, + // but often this will fall back to the first workspace folder. + this.updateCwd(Iterable.find(result.tests, t => !!t.item.uri)?.item.uri); + + const terminal = await this.makeTerminal(); + if (result instanceof LiveTestResult) { + let hadData = false; + for (const buffer of task.output.buffers) { + hadData ||= buffer.byteLength > 0; + terminal.write(buffer.buffer); + } + if (!hadData && !task.running) { + this.writeNotice(terminal, localize('runNoOutout', 'The test run did not record any output.')); + } + } else { + this.writeNotice(terminal, localize('runNoOutputForPast', 'Test output is only available for new test runs.')); + } + + this.attachTerminalToDom(terminal); + this.outputDataListener.value = task.output.onDidWriteData(e => terminal.write(e.buffer)); + } + } + + private updateCwd(testUri?: URI) { + const wf = (testUri && this.workspaceContext.getWorkspaceFolder(testUri)) + || this.workspaceContext.getWorkspace().folders[0]; + if (wf) { + this.terminalCwd.value = wf.uri.fsPath; + } + } + + private writeNotice(terminal: IDetachedXtermTerminal, str: string) { + terminal.write(`\x1b[2m${str}\x1b[0m`); + } + + private attachTerminalToDom(terminal: IDetachedXtermTerminal) { + terminal.write('\x1b[?25l'); // hide cursor + requestAnimationFrame(() => this.layoutTerminal(terminal)); + terminal.attachToElement(this.container, { enableGpu: false }); } private clear() { - this.model.clear(); - this.widget.clear(); + this.outputDataListener.clear(); + this.terminal.clear(); } public layout(dimensions: dom.IDimension) { - this.dimension = dimensions; - this.widget.value?.layout(dimensions); + this.dimensions = dimensions; + if (this.terminal.value) { + this.layoutTerminal(this.terminal.value, dimensions.width, dimensions.height); + } + } + + private layoutTerminal( + xterm: IDetachedXtermTerminal, + width = this.dimensions?.width ?? this.container.clientWidth, + height = this.dimensions?.height ?? this.container.clientHeight + ) { + width -= 10 + 20; // scrollbar width + margin + const scaled = getXtermScaledDimensions(xterm.getFont(), width, height); + if (scaled) { + xterm.resize(scaled.cols, scaled.rows); + } } } @@ -1420,7 +1528,7 @@ class OutputPeekTree extends Disposable { ) { super(); - this.treeActions = instantiationService.createInstance(TreeActionsProvider, options.showRevealLocationOnMessages); + this.treeActions = instantiationService.createInstance(TreeActionsProvider, options.showRevealLocationOnMessages, this.requestReveal,); const diffIdentityProvider: IIdentityProvider = { getId(e: TreeElement) { return e.id; @@ -1515,6 +1623,10 @@ class OutputPeekTree extends Disposable { const resultNode = cc.get(result)! as TestResultElement; const disposable = new DisposableStore(); disposable.add(result.onNewTask(() => { + if (result.tasks.length === 1) { + this.requestReveal.fire(new TaskSubject(result.id, 0)); // reveal the first task in new runs + } + if (this.tree.hasElement(resultNode)) { this.tree.setChildren(resultNode, getResultChildren(result), { diffIdentityProvider }); } @@ -1617,9 +1729,7 @@ class OutputPeekTree extends Disposable { })); this._register(this.tree.onDidOpen(async e => { - if (e.element instanceof TaskElement) { - this.requestReveal.fire(new TaskSubject(e.element.results.id, e.element.index)); - } else if (e.element instanceof TestMessageElement) { + if (e.element instanceof TestMessageElement) { this.requestReveal.fire(new MessageSubject(e.element.result.id, e.element.test, e.element.taskIndex, e.element.messageIndex)); } })); @@ -1751,8 +1861,8 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ITestingOutputTerminalService private readonly testTerminalService: ITestingOutputTerminalService, @IMenuService private readonly menuService: IMenuService, @ICommandService private readonly commandService: ICommandService, @ITestProfileService private readonly testProfileService: ITestProfileService, @@ -1779,7 +1889,7 @@ class TreeActionsProvider { localize('testing.showResultOutput', "Show Result Output"), ThemeIcon.asClassName(Codicon.terminal), undefined, - () => this.testTerminalService.open(element.results, element.index) + () => this.requestReveal.fire(new TaskSubject(element.results.id, element.index)), )); } @@ -1791,7 +1901,7 @@ class TreeActionsProvider { localize('testing.showResultOutput', "Show Result Output"), ThemeIcon.asClassName(Codicon.terminal), undefined, - () => this.testTerminalService.open(element.value, 0) + () => this.requestReveal.fire(new TaskSubject(element.value.id, 0)), )); } @@ -1869,15 +1979,6 @@ class TreeActionsProvider { }), )); } - if (element.marker !== undefined) { - primary.push(new Action( - 'testing.outputPeek.showMessageInTerminal', - localize('testing.showMessageInTerminal', "Show Output in Terminal"), - ThemeIcon.asClassName(Codicon.terminal), - undefined, - () => this.testTerminalService.open(element.result, element.taskIndex, element.marker), - )); - } } const result = { primary, secondary }; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts b/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts deleted file mode 100644 index 89bcc961b5e..00000000000 --- a/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts +++ /dev/null @@ -1,293 +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 { DeferredPromise } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { language } from 'vs/base/common/platform'; -import { isDefined } from 'vs/base/common/types'; -import { localize } from 'vs/nls'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessDataEvent, IProcessPropertyMap, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, ProcessPropertyType, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; -import { IViewsService } from 'vs/workbench/common/views'; -import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; -import { testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons'; -import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult'; -import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; -import { getMarkId } from 'vs/workbench/contrib/testing/common/testTypes'; - - -export interface ITestingOutputTerminalService { - _serviceBrand: undefined; - - /** - * Opens a terminal for the given test's output. Optionally, scrolls to and - * selects the given marker in the test results. - */ - open(result: ITestResult, taskIndex: number, marker?: number): Promise; -} - -const friendlyDate = (date: number) => { - const d = new Date(date); - return d.getHours() + ':' + String(d.getMinutes()).padStart(2, '0') + ':' + String(d.getSeconds()).padStart(2, '0'); -}; - -const getTitle = (result: ITestResult | undefined, taskIndex: number | undefined) => { - if (!result || taskIndex === undefined) { - return genericTitle; - } - - const task = result.tasks[taskIndex]; - if (result.tasks.length < 2 || !task?.name) { - return localize('testOutputTerminalTitleWithDate', 'Test Output at {0}', friendlyDate(result.completedAt ?? Date.now())); - } - - return localize('testOutputTerminalTitleWithDateAndTaskName', '{0} at {1}', task.name, friendlyDate(result.completedAt ?? Date.now())); -}; - -const genericTitle = localize('testOutputTerminalTitle', 'Test Output'); - -export const ITestingOutputTerminalService = createDecorator('ITestingOutputTerminalService'); - -export class TestingOutputTerminalService implements ITestingOutputTerminalService { - _serviceBrand: undefined; - - private outputTerminals = new WeakMap(); - - constructor( - @ITerminalService private readonly terminalService: ITerminalService, - @ITerminalGroupService private readonly terminalGroupService: ITerminalGroupService, - @ITerminalEditorService private readonly terminalEditorService: ITerminalEditorService, - @ITestResultService resultService: ITestResultService, - @IViewsService private viewsService: IViewsService, - ) { - - const newTaskListener = new MutableDisposable(); - - // If a result terminal is currently active and we start a new test run, - // stream live results there automatically. - resultService.onResultsChanged(evt => { - if (!('started' in evt)) { - return; - } - - newTaskListener.value = evt.started.onNewTask(taskIndex => { - const active = this.terminalService.activeInstance; - if (!active) { - return; - } - - const pane = this.viewsService.getActiveViewWithId(TERMINAL_VIEW_ID); - if (!pane) { - return; - } - - const output = this.outputTerminals.get(active); - if (output && output.ended) { - this.showResultsInTerminal(active, output, evt.started, taskIndex); - } - }); - }); - } - - /** - * @inheritdoc - */ - public async open(result: ITestResult | undefined, taskIndex: number | undefined, marker?: number): Promise { - const testOutputPtys = this.terminalService.instances - .map(t => { - const output = this.outputTerminals.get(t); - return output ? [t, output] as const : undefined; - }) - .filter(isDefined); - - // If there's an existing terminal for the attempted reveal, show that instead. - const existing = testOutputPtys.find(([, o]) => o.resultId === result?.id && o.taskIndex === taskIndex); - if (existing) { - this.terminalService.setActiveInstance(existing[0]); - if (existing[0].target === TerminalLocation.Editor) { - this.terminalEditorService.revealActiveEditor(); - } else { - this.terminalGroupService.showPanel(); - } - - this.revealMarker(existing[0], marker); - return; - } - - // Try to reuse ended terminals, otherwise make a new one - const ended = testOutputPtys.find(([, o]) => o.ended); - if (ended) { - ended[1].clear(); - this.showResultsInTerminal(ended[0], ended[1], result, taskIndex); - return; - } - - const output = new TestOutputProcess(); - this.showResultsInTerminal(await this.terminalService.createTerminal({ - config: { - isFeatureTerminal: true, - icon: testingViewIcon, - customPtyImplementation: () => output, - name: getTitle(result, taskIndex), - }, - }), output, result, taskIndex, marker); - } - - private async showResultsInTerminal(terminal: ITerminalInstance, output: TestOutputProcess, result: ITestResult | undefined, taskIndex: number | undefined, thenSelectMarker?: number) { - this.outputTerminals.set(terminal, output); - const title = getTitle(result, taskIndex); - output.resetFor(result?.id, taskIndex, title); - terminal.rename(title); - - this.terminalService.setActiveInstance(terminal); - if (terminal.target === TerminalLocation.Editor) { - this.terminalEditorService.revealActiveEditor(); - } else { - this.terminalGroupService.showPanel(); - } - - await output.started; - - if (!result || taskIndex === undefined) { - // seems like it takes a tick for listeners to be registered - output.ended = true; - setTimeout(() => output.pushData(localize('testNoRunYet', '\r\nNo tests have been run, yet.\r\n'))); - return; - } - - - const testOutput = result.tasks[taskIndex].output; - - let hadData = false; - for (const d of testOutput.buffers) { - hadData = true; - output.pushData(d.toString()); - } - - const disposable = new DisposableStore(); - disposable.add(testOutput.onDidWriteData(d => { - hadData = true; - output.pushData(d.toString()); - })); - - testOutput.endPromise.then(() => { - if (disposable.isDisposed) { - return; - } - if (!hadData) { - output.pushData(`\x1b[2m${localize('runNoOutout', 'The test run did not record any output.')}\x1b[0m`); - } - - const completedAt = result.completedAt ? new Date(result.completedAt) : new Date(); - const text = localize('runFinished', 'Test run finished at {0}', completedAt.toLocaleString(language)); - output.pushData(`\r\n\r\n\x1b[1m> ${text} <\x1b[0m\r\n\r\n`); - output.ended = true; - this.revealMarker(terminal, thenSelectMarker); - disposable.dispose(); - }); - - disposable.add(terminal.onDisposed(() => { - disposable.dispose(); - })); - } - - private revealMarker(terminal: ITerminalInstance, marker?: number) { - if (marker !== undefined) { - terminal.scrollToMark(getMarkId(marker, true), getMarkId(marker, false), true); - } - } -} - -class TestOutputProcess extends Disposable implements ITerminalChildProcess { - onProcessOverrideDimensions?: Event | undefined; - onProcessResolvedShellLaunchConfig?: Event | undefined; - onDidChangeHasChildProcesses?: Event | undefined; - onDidChangeProperty = Event.None; - private processDataEmitter = this._register(new Emitter()); - private readonly startedDeferred = new DeferredPromise(); - /** Whether the associated test has ended (indicating the terminal can be reused) */ - public ended = true; - /** Result currently being displayed */ - public resultId: string | undefined; - /** Task currently being displayed */ - public taskIndex: number | undefined; - /** Promise resolved when the terminal is ready to take data */ - public readonly started = this.startedDeferred.p; - - public pushData(data: string | IProcessDataEvent) { - this.processDataEmitter.fire(data); - } - - public clear() { - this.processDataEmitter.fire('\x1bc'); - } - - public resetFor(resultId: string | undefined, taskIndex: number | undefined, title: string) { - this.ended = false; - this.resultId = resultId; - this.taskIndex = taskIndex; - } - - //#region implementation - public readonly id = 0; - public readonly shouldPersist = false; - - public readonly onProcessData = this.processDataEmitter.event; - public readonly onProcessExit = this._register(new Emitter()).event; - private readonly _onProcessReady = this._register(new Emitter()); - public readonly onProcessReady = this._onProcessReady.event; - public readonly onProcessShellTypeChanged = this._register(new Emitter()).event; - - public start(): Promise { - this.startedDeferred.complete(); - this._onProcessReady.fire({ pid: -1, cwd: '', windowsPty: undefined }); - return Promise.resolve(undefined); - } - public shutdown(): void { - // no-op - } - public input(): void { - // not supported - } - public processBinary(): Promise { - return Promise.resolve(); - } - public resize(): void { - // no-op - } - public clearBuffer(): void | Promise { - // no-op - } - public acknowledgeDataEvent(): void { - // no-op, flow control not currently implemented - } - public setUnicodeVersion(): Promise { - // no-op - return Promise.resolve(); - } - - public getInitialCwd(): Promise { - return Promise.resolve(''); - } - - public getCwd(): Promise { - return Promise.resolve(''); - } - - public getLatency(): Promise { - return Promise.resolve(0); - } - - public refreshProperty(property: ProcessPropertyType): Promise { - throw new Error(`refreshProperty is not suppported in TestOutputProcesses. property: ${property}`); - } - - public updateProperty(property: ProcessPropertyType, value: any): Promise { - throw new Error(`updateProperty is not suppported in TestOutputProcesses. property: ${property}, value: ${value}`); - } - //#endregion -} diff --git a/src/vs/workbench/contrib/testing/common/configuration.ts b/src/vs/workbench/contrib/testing/common/configuration.ts index fef2b0bd173..8ab51e92ec8 100644 --- a/src/vs/workbench/contrib/testing/common/configuration.ts +++ b/src/vs/workbench/contrib/testing/common/configuration.ts @@ -17,7 +17,8 @@ export const enum TestingConfigKeys { GutterEnabled = 'testing.gutterEnabled', SaveBeforeTest = 'testing.saveBeforeTest', AlwaysRevealTestOnStateChange = 'testing.alwaysRevealTestOnStateChange', - CountBadge = 'testing.countBadge' + CountBadge = 'testing.countBadge', + ShowAllMessages = 'testing.showAllMessages', } export const enum AutoOpenTesting { @@ -71,6 +72,11 @@ export const testingConfiguration: IConfigurationNode = { localize('testing.automaticallyOpenPeekView.never', "Never automatically open."), ], }, + [TestingConfigKeys.ShowAllMessages]: { + description: localize('testing.showAllMessages', "Controls whether to show messages from all test runs."), + type: 'boolean', + default: true, + }, [TestingConfigKeys.AutoOpenPeekViewDuringContinuousRun]: { description: localize('testing.automaticallyOpenPeekViewDuringContinuousRun', "Controls whether to automatically open the Peek view during continuous run mode."), type: 'boolean', @@ -154,6 +160,7 @@ export interface ITestingConfiguration { [TestingConfigKeys.SaveBeforeTest]: boolean; [TestingConfigKeys.OpenTesting]: AutoOpenTesting; [TestingConfigKeys.AlwaysRevealTestOnStateChange]: boolean; + [TestingConfigKeys.ShowAllMessages]: boolean; } export const getTestingConfiguration = (config: IConfigurationService, key: K) => config.getValue(key); diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts index 575bde3b87b..5df2e350a0e 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts @@ -78,11 +78,12 @@ export class NativeDialogHandler extends AbstractDialogHandler { const detailString = (useAgo: boolean): string => { return localize({ key: 'aboutDetail', comment: ['Electron, Chromium, Node.js and V8 are product names that need no translation'] }, - "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChromium: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", + "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nElectronBuildId: {4}\nChromium: {5}\nNode.js: {6}\nV8: {7}\nOS: {8}", version, this.productService.commit || 'Unknown', this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown', process.versions['electron'], + process.versions['microsoft-build'], process.versions['chrome'], process.versions['node'], process.versions['v8'], diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index d4db167e5e5..59c293fa8b1 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -228,7 +228,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { let sum = 0; for (let i = 0; i < COUNT; i++) { - const sw = StopWatch.create(true); + const sw = StopWatch.create(); await proxy.test_latency(i); sw.stop(); sum += sw.elapsed(); @@ -248,7 +248,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { for (let i = 0; i < buff.byteLength; i++) { buff.writeUInt8(i, value); } - const sw = StopWatch.create(true); + const sw = StopWatch.create(); await proxy.test_up(buff); sw.stop(); return ExtensionHostManager._convert(SIZE, sw.elapsed()); @@ -257,7 +257,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { private async _measureDown(proxy: IExtensionHostProxy): Promise { const SIZE = 10 * 1024 * 1024; // 10MB - const sw = StopWatch.create(true); + const sw = StopWatch.create(); await proxy.test_down(SIZE); sw.stop(); return ExtensionHostManager._convert(SIZE, sw.elapsed()); diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts index 59d8bf7ef8b..e8c8239b35f 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts @@ -39,7 +39,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { public async detectLanguage(uri: string, langBiases: Record | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise { const languages: string[] = []; const confidences: number[] = []; - const stopWatch = new StopWatch(true); + const stopWatch = new StopWatch(); const documentTextSample = this.getTextForDetection(uri); if (!documentTextSample) { return; } diff --git a/src/vs/workbench/services/secrets/browser/secretStorageService.ts b/src/vs/workbench/services/secrets/browser/secretStorageService.ts index bdc68b1f4a1..e28cec9b7d6 100644 --- a/src/vs/workbench/services/secrets/browser/secretStorageService.ts +++ b/src/vs/workbench/services/secrets/browser/secretStorageService.ts @@ -5,7 +5,9 @@ import { IEncryptionService } from 'vs/platform/encryption/common/encryptionService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { ISecretStorageProvider, ISecretStorageService, SecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; @@ -18,9 +20,11 @@ export class BrowserSecretStorageService extends SecretStorageService { @IStorageService storageService: IStorageService, @IEncryptionService encryptionService: IEncryptionService, @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, @ILogService logService: ILogService ) { - super(storageService, encryptionService, logService); + super(storageService, encryptionService, instantiationService, notificationService, logService); if (environmentService.options?.secretStorageProvider) { this._secretStorageProvider = environmentService.options.secretStorageProvider; diff --git a/yarn.lock b/yarn.lock index c3364a6dbd2..cece00d0a16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1240,10 +1240,10 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vscode/gulp-electron@^1.34.0": - version "1.34.0" - resolved "https://registry.yarnpkg.com/@vscode/gulp-electron/-/gulp-electron-1.34.0.tgz#59e00502e0a9703d1a0b7184dbd98ffe5125d2f4" - integrity sha512-zqLmueXYIqwgv0QhDdiDJOQYBYlCKE6Vu9KovMJ1ONZDrgb2CxUdaqxJRnROY18n6XO9RmNWodnplvSWkM+RVQ== +"@vscode/gulp-electron@^1.35.0": + version "1.35.0" + resolved "https://registry.yarnpkg.com/@vscode/gulp-electron/-/gulp-electron-1.35.0.tgz#b66aa307b1edefb0b37988faf29b82ab9974c466" + integrity sha512-FjRdAlfJBflFs28RYBaJ3gK/LHFZ4T34nP/FnKZrwNjWq5ojDC9WiwOMDHaG7wP7AKBcxh267Gz2epghMeO1ng== dependencies: "@electron/get" "^2.0.2" "@octokit/rest" "^18.0.14" @@ -3526,10 +3526,10 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.207.tgz#9c3310ebace2952903d05dcaba8abe3a4ed44c01" integrity sha512-piH7MJDJp4rJCduWbVvmUd59AUne1AFBJ8JaRQvk0KzNTSUnZrVXHCZc+eg+CGE4OujkcLJznhGKD6tuAshj5Q== -electron@22.3.10: - version "22.3.10" - resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.10.tgz#bf2b98fbb452f41b057ffcd4e57678e363f9735c" - integrity sha512-gh7PtSh+rfxHfM4dzPiEO+k1NRo07FvaK/jXG3HzuODrpTTEhC9rsE+AJGrTKQU6Nz7GorseMvnvs8PnUQPPTw== +electron@22.3.11: + version "22.3.11" + resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.11.tgz#db68c3f820eab9868ccf65bd4604c2805c4c93b9" + integrity sha512-4PW1rJRUckJUCxTXRJkzJ7qlGTZ8Qfwoke5aFlaGccmn/zViuE9iSCg9zqIx00rzsbF9R5j8j9V4tAqyqjjJRA== dependencies: "@electron/get" "^2.0.0" "@types/node" "^16.11.26" @@ -10453,45 +10453,45 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-canvas@0.5.0-beta.1: - version "0.5.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.1.tgz#b5ae185741423715460a66029c944b4dededfab0" - integrity sha512-A7yjIpyTcOh8ckPJw1YFDvwbTbQ+grM+kTtutOvu5LjLSV9EoCHX17kVoiT2V29ywF7KJMeLYwfBwyFE3uA3QQ== +xterm-addon-canvas@0.5.0-beta.2: + version "0.5.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.2.tgz#1b83c2a9a306766c47a4f80b8c65cc9ee5f5a5c4" + integrity sha512-oTb/2krdbHYGxH2X6yiBZzAB/1WB+apUu4nXHdhBnht20bl8E+YVWqg95D4o0Gl+QJI+XOfB3mqmWaBx1x531A== xterm-addon-image@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.4.1.tgz#ec8f750af48005ad641c1128fa1f551ac198472a" integrity sha512-iJpYyvtbHg4oXSv+D6J73ZfCjnboZpbZ567MLplXDBlYSUknv3kvPTfVMPJATV7Zsx7+bDgyXboCh9vsDf/m/w== -xterm-addon-search@0.13.0-beta.1: - version "0.13.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.1.tgz#9fb6ede402d4c369d59d5d6faefe54a05b125bcf" - integrity sha512-rdOIhwkfRASqTriUO8QP9UY0p6BosLMv1NXTZqhgq3/5xAXx4VZg6mlQjTRGnUz/GJIN1jU9e/Vp20SpocP/Hw== +xterm-addon-search@0.13.0-beta.2: + version "0.13.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.2.tgz#c984a35312acad4ce768d17bc49adffa90eece61" + integrity sha512-+VoPhIRmfiX2uh2t6xD/RJtBYjVjrkNa3dKQnOYEp4UbYzDjK57rZX652mnZ82TQfk/juxf7v+jV5aRdNLZVbA== -xterm-addon-serialize@0.11.0-beta.1: - version "0.11.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.1.tgz#a4bc1ef5d8b8db0180c07f071ce543536d806db1" - integrity sha512-2I9Dq49nXUc6ymznwJp8SUsDq5owUdYviUy11HzLh35baDjzbG31CCu5Gs8KSlfUxpNRr3BxaV5/hx7MRPu7Qg== +xterm-addon-serialize@0.11.0-beta.2: + version "0.11.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.2.tgz#fff924decfbf1bc08434317894f985fef7bb260b" + integrity sha512-tN4IT2e+EIpsoFpMONUh1OAuoVAcV7AYOLsqMKgH6GNWB1D/LKGo3cwjpw1vwRZzDJJcCcLxYgxlUzhPbDbLxQ== xterm-addon-unicode11@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.1: - version "0.16.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.1.tgz#e2b41c6b5f838724a5cb3cfa4231e2d1b8f3f130" - integrity sha512-iJK+Uk+23Mh84BNa/44JqAdPESdNKN0ONfw6UztmDk2HTvsy47sU+d/lgF2kOcuI3ew2tRzK9YlupOUhVPwe9g== +xterm-addon-webgl@0.16.0-beta.2: + version "0.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.2.tgz#30489ef235405255ee54077002c90553531870d8" + integrity sha512-DAt4E/QI1w34ToBhcDj0vaZOAHOO+ffwMt2HGDAB7amPXRcMb0LBIjLpyZhB9sD4tbIgsE0vuqZi1R9vKxZwbg== -xterm-headless@5.3.0-beta.1: - version "5.3.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.1.tgz#8c7db703b9f57496c2f052411721c00909b08e8b" - integrity sha512-6rsv6l44hLL9Eg2UrfAbCiZcAucdHuPyIsovl2BEmluo4chwd4LD7VINRlPV/x8ML2HgD9SohFyNs5BQAc07Gg== +xterm-headless@5.3.0-beta.3: + version "5.3.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.3.tgz#153cf330082f4b2aae64ff736ef0b62d93c30da8" + integrity sha512-4i/bpFoAn4D4ZA4g8RKrJdhq2EcB1HN2E25yUg3omRbWCOZ2Gp9nAn+62LYzX5rvGqdNbpUTRJLX0lKwEFyLFw== -xterm@5.3.0-beta.1: - version "5.3.0-beta.1" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.1.tgz#68e76f2818965592c60bb269360f1fc37219f4ae" - integrity sha512-2v/Qmk1A0wO5oouRWUWZ3wxqtfFjxsQbZ1sWxPGJTQvoTSdkORLG44gxrU+Sk2jB5Ojz+3Kg42bnfFghsXVzlw== +xterm@5.3.0-beta.3: + version "5.3.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.3.tgz#1a1aaf9a57afe4dcf86e87d8dc85e80a41d68644" + integrity sha512-NGxpV25U2W/KKk6M5V2OXuLgrKY+w05ABi66ZEYuCTi7ux1Qv0z+jm7bkgzk1pGGiTVLG+90OGr2nrhbFr5Y4w== y18n@^3.2.1: version "3.2.2"