diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f35e8a4cd93f026d13c9a6ca4a0b46193ff63205..e6b09a4e9fc07f8712f2c105eb5bfe882512ea48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,8 +109,8 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules13-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules13- + key: ${{ runner.os }}-cacheNodeModules14-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules14- - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -165,8 +165,8 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules13-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules13- + key: ${{ runner.os }}-cacheNodeModules14-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules14- - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -218,8 +218,8 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules13-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules13- + key: ${{ runner.os }}-cacheNodeModules14-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules14- - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -247,6 +247,9 @@ jobs: - name: Compile /build/ run: yarn --cwd build compile + - name: Run eslint + run: yarn eslint + - name: Run Monaco Editor Checks run: yarn monaco-compile-check diff --git a/.lsifrc.json b/.lsifrc.json new file mode 100644 index 0000000000000000000000000000000000000000..5b992e89ca8b2b6be4d6d9acdd9dfb29caea0728 --- /dev/null +++ b/.lsifrc.json @@ -0,0 +1,6 @@ +{ + "project": "src/tsconfig.json", + "source": "./package.json", + "package": "package.json", + "out": "vscode.lsif" +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f7e572651077cb653d343d060a1990e0dcef8f7..a45b9d0a407ce6878d39893bab5994f4bca7bd05 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -198,6 +198,7 @@ "type": "pwa-chrome", "request": "launch", "name": "Launch VS Code Internal", + "trace": true, "windows": { "runtimeExecutable": "${workspaceFolder}/scripts/code.bat" }, diff --git a/.yarnrc b/.yarnrc index b2b326324a6381589c50a66253158e892060a294..ac09cae4c7f8c908cadf9d10dc8edc4ea4d0e9d6 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://electronjs.org/headers" -target "12.0.13" +target "13.1.6" runtime "electron" diff --git a/README.md b/README.md index 9f6a0b00af299a6b7e0df7c87818b943f4d4d2dd..1883fe4b76bf781f749c5dc23d84f3728bf160c2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Visual Studio Code - Open Source ("Code - OSS") -[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/microsoft/vscode) [![Feature Requests](https://img.shields.io/github/issues/microsoft/vscode/feature-request.svg)](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) [![Bugs](https://img.shields.io/github/issues/microsoft/vscode/bug.svg)](https://github.com/microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug) [![Gitter](https://img.shields.io/badge/chat-on%20gitter-yellow.svg)](https://gitter.im/Microsoft/vscode) diff --git a/build/.cachesalt b/build/.cachesalt index 013244143e8f2d0cb8c78871f4bd681d1f956f44..3ceb6423cc563a68912e9e6d90937f3bfd5c246b 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2021-04-07T03:52:18.011Z +2021-05-26T10:17:08.678Z diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 8dcf809b39174ccb5a086291aee5134cd8954605..2648235fcc8eae0e83b4d3ff01215a09a3685559 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -390,7 +390,7 @@ async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { reject(); } else { - reporter(stats.toJson()); + reporter(stats === null || stats === void 0 ? void 0 : stats.toJson()); } }); } @@ -401,7 +401,7 @@ async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { reject(); } else { - reporter(stats.toJson()); + reporter(stats === null || stats === void 0 ? void 0 : stats.toJson()); resolve(); } }); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index bd7b508bb03c504cd47c272b3eda7a2f8b2888e5..8e091cae74b2f74c0446ae533274a2c4f427eda8 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -475,7 +475,7 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp if (err) { reject(); } else { - reporter(stats.toJson()); + reporter(stats?.toJson()); } }); } else { @@ -484,7 +484,7 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp fancyLog.error(err); reject(); } else { - reporter(stats.toJson()); + reporter(stats?.toJson()); resolve(); } }); diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 63f4af4e0264ddaf49a1f39d629a2b12a7740823..c4d42bacfaeebdc324ec474d6cb81b488e6b0884 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -46,6 +46,10 @@ "name": "vs/workbench/contrib/callHierarchy", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/typeHierarchy", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/codeActions", "project": "vscode-workbench" diff --git a/build/monaco/monaco.webpack.config.js b/build/monaco/monaco.webpack.config.js index 974a341a197e1250fd2b85cdcab15ce3c529f1b2..9e075aad2aa5185c0098cfd7b2039728cf30ee57 100644 --- a/build/monaco/monaco.webpack.config.js +++ b/build/monaco/monaco.webpack.config.js @@ -33,7 +33,6 @@ module.exports = { stats: { all: false, modules: true, - maxModules: 0, errors: true, warnings: true, // our additional options diff --git a/build/package.json b/build/package.json index 7fb5f09c70af7495a146e8e7a7559a086246771f..9f28a2a929a692c7ef850493aa362e8c97a98eda 100644 --- a/build/package.json +++ b/build/package.json @@ -57,7 +57,7 @@ "plist": "^3.0.1", "source-map": "0.6.1", "tmp": "^0.2.1", - "typescript": "^4.4.0-dev.20210708", + "typescript": "^4.4.0-dev.20210713", "vsce": "1.48.0", "vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58" }, diff --git a/build/polyfills/vscode-nls.js b/build/polyfills/vscode-nls.js deleted file mode 100644 index b89250102afca4cb86b5ec6beed6df1b8874ef9f..0000000000000000000000000000000000000000 --- a/build/polyfills/vscode-nls.js +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -Object.defineProperty(exports, "__esModule", { value: true }); - -function format(message, args) { - let result; - // if (isPseudo) { - // // FF3B and FF3D is the Unicode zenkaku representation for [ and ] - // message = '\uFF3B' + message.replace(/[aouei]/g, '$&$&') + '\uFF3D'; - // } - if (args.length === 0) { - result = message; - } - else { - result = message.replace(/\{(\d+)\}/g, function (match, rest) { - let index = rest[0]; - let arg = args[index]; - let replacement = match; - if (typeof arg === 'string') { - replacement = arg; - } - else if (typeof arg === 'number' || typeof arg === 'boolean' || arg === void 0 || arg === null) { - replacement = String(arg); - } - return replacement; - }); - } - return result; -} - -function localize(key, message) { - let args = []; - for (let _i = 2; _i < arguments.length; _i++) { - args[_i - 2] = arguments[_i]; - } - return format(message, args); -} - -function loadMessageBundle(file) { - return localize; -} - -let MessageFormat; -(function (MessageFormat) { - MessageFormat["file"] = "file"; - MessageFormat["bundle"] = "bundle"; - MessageFormat["both"] = "both"; -})(MessageFormat = exports.MessageFormat || (exports.MessageFormat = {})); -let BundleFormat; -(function (BundleFormat) { - // the nls.bundle format - BundleFormat["standalone"] = "standalone"; - BundleFormat["languagePack"] = "languagePack"; -})(BundleFormat = exports.BundleFormat || (exports.BundleFormat = {})); - -exports.loadMessageBundle = loadMessageBundle; -function config(opts) { - if (opts) { - if (isString(opts.locale)) { - options.locale = opts.locale.toLowerCase(); - options.language = options.locale; - resolvedLanguage = undefined; - resolvedBundles = Object.create(null); - } - if (opts.messageFormat !== undefined) { - options.messageFormat = opts.messageFormat; - } - if (opts.bundleFormat === BundleFormat.standalone && options.languagePackSupport === true) { - options.languagePackSupport = false; - } - } - isPseudo = options.locale === 'pseudo'; - return loadMessageBundle; -} -exports.config = config; diff --git a/build/yarn.lock b/build/yarn.lock index 3a23c6c2a589e4ec6da93ec0a83573f71d528e8a..dc75e2fd8d31023f6fe2d1288c7c47963c4e1e47 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2047,10 +2047,10 @@ typescript@^4.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== -typescript@^4.4.0-dev.20210708: - version "4.4.0-dev.20210708" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-dev.20210708.tgz#0043aa6d3b81c111c6215477a31774b5a864e7e1" - integrity sha512-jGNamsvrU8F8KjMawCauI7bQeUPKYdyIp4yiEsKv8Uk1gt494FN09wgtH9wbbT0qK7a7lel7A/N/DodpPWK/6Q== +typescript@^4.4.0-dev.20210713: + version "4.4.0-dev.20210713" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-dev.20210713.tgz#80e5d4c550e464e6aad8c0e2aa4cf42d67ce549d" + integrity sha512-Z8zYMhAwLa7mpc3LP3Z+fOTmXsRqkvgepB+dEg08g9tI9B+kR0izmPZOlcfBN7GlyxmZe5qpnnGmkhxZeHxa3g== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" diff --git a/cgmanifest.json b/cgmanifest.json index ef37a43314a88df3ba6c5c3269091115238e0a34..adee4b3c0a5eca87df34f04c7df044c574c7bd5d 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "cd7a46bf02a768a1aabf9443f6ee469bc6e28e7c" + "commitHash": "7a7e35991d61ce564ed3641222da2c4ed7a65535" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "89.0.4389.128" + "version": "91.0.4472.124" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "14ae30aa9b2619358298b525acd398446b385151" + "commitHash": "ddc44e1af4fa6bf0c4c05f3dd48270ef308bc470" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "12.0.13" + "version": "13.1.6" }, { "component": { diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 80e627e5f9390d8718f483eb5e9f4c5be76ec52a..096e3014ae174f000379dbaafcb720674c8dce22 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "jsonc-parser": "^2.2.1", - "vscode-nls": "^4.1.1" + "vscode-nls": "^5.0.0" }, "capabilities": { "virtualWorkspaces": true, diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index d7215349a93887e35280db0516ad6bd50cb53030..d4882d39e4b4be3f83b78d38a60a0ecbff6a0912 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -12,7 +12,7 @@ jsonc-parser@^2.2.1: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== -vscode-nls@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" - integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/css-language-features/server/extension-browser.webpack.config.js b/extensions/css-language-features/server/extension-browser.webpack.config.js index 38816259ddf9c1cd28cfa3ab55f9d2955e29e294..aded41897a3f6484f89d728bbeb0023c3026105b 100644 --- a/extensions/css-language-features/server/extension-browser.webpack.config.js +++ b/extensions/css-language-features/server/extension-browser.webpack.config.js @@ -18,6 +18,7 @@ module.exports = withBrowserDefaults({ output: { filename: 'cssServerMain.js', path: path.join(__dirname, 'dist', 'browser'), - libraryTarget: 'var' + libraryTarget: 'var', + library: 'serverExportVar' } }); diff --git a/extensions/css-language-features/server/src/browser/cssServerMain.ts b/extensions/css-language-features/server/src/browser/cssServerMain.ts index 13284fadcd956faddaf1d1a955330d67f242c20e..09560a795802e9a864bc7f44a423b3c3c32c6b05 100644 --- a/extensions/css-language-features/server/src/browser/cssServerMain.ts +++ b/extensions/css-language-features/server/src/browser/cssServerMain.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser'; -import { startServer } from '../cssServer'; +import { createConnection, BrowserMessageReader, BrowserMessageWriter, Disposable } from 'vscode-languageserver/browser'; +import { RuntimeEnvironment, startServer } from '../cssServer'; declare let self: any; @@ -13,4 +13,17 @@ const messageWriter = new BrowserMessageWriter(self); const connection = createConnection(messageReader, messageWriter); -startServer(connection, {}); +const runtime: RuntimeEnvironment = { + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setTimeout(callback, 0, ...args); + return { dispose: () => clearTimeout(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + } +}; + +startServer(connection, runtime); diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index cdfe37ff6a145fdb13e32d14938fbb744ced571d..2108b64dd2c7391686654a96e09d9215af344407 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { - Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType + Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice'; @@ -25,8 +25,12 @@ export interface Settings { } export interface RuntimeEnvironment { - file?: RequestService; - http?: RequestService + readonly file?: RequestService; + readonly http?: RequestService; + readonly timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable; + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; + } } export function startServer(connection: Connection, runtime: RuntimeEnvironment) { @@ -150,7 +154,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) documents.all().forEach(triggerValidation); } - const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; + const pendingValidationRequests: { [uri: string]: Disposable } = {}; const validationDelayMs = 500; // The content of a text document has changed. This event is emitted @@ -168,14 +172,14 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) function cleanPendingValidation(textDocument: TextDocument): void { const request = pendingValidationRequests[textDocument.uri]; if (request) { - clearTimeout(request); + request.dispose(); delete pendingValidationRequests[textDocument.uri]; } } function triggerValidation(textDocument: TextDocument): void { cleanPendingValidation(textDocument); - pendingValidationRequests[textDocument.uri] = setTimeout(() => { + pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => { delete pendingValidationRequests[textDocument.uri]; validateTextDocument(textDocument); }, validationDelayMs); @@ -203,7 +207,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } connection.onCompletion((textDocumentPosition, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(textDocumentPosition.textDocument.uri); if (document) { const [settings,] = await Promise.all([getDocumentSettings(document), dataProvidersReady]); @@ -216,7 +220,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onHover((textDocumentPosition, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(textDocumentPosition.textDocument.uri); if (document) { const [settings,] = await Promise.all([getDocumentSettings(document), dataProvidersReady]); @@ -228,7 +232,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentSymbol((documentSymbolParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(documentSymbolParams.textDocument.uri); if (document) { await dataProvidersReady; @@ -240,7 +244,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDefinition((documentDefinitionParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(documentDefinitionParams.textDocument.uri); if (document) { await dataProvidersReady; @@ -252,7 +256,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentHighlight((documentHighlightParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(documentHighlightParams.textDocument.uri); if (document) { await dataProvidersReady; @@ -265,7 +269,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.onDocumentLinks(async (documentLinkParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(documentLinkParams.textDocument.uri); if (document) { await dataProvidersReady; @@ -279,7 +283,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.onReferences((referenceParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(referenceParams.textDocument.uri); if (document) { await dataProvidersReady; @@ -291,7 +295,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onCodeAction((codeActionParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(codeActionParams.textDocument.uri); if (document) { await dataProvidersReady; @@ -303,7 +307,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentColor((params, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { await dataProvidersReady; @@ -315,7 +319,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onColorPresentation((params, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { await dataProvidersReady; @@ -327,7 +331,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onRenameRequest((renameParameters, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(renameParameters.textDocument.uri); if (document) { await dataProvidersReady; @@ -339,7 +343,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onFoldingRanges((params, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { await dataProvidersReady; @@ -350,7 +354,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onSelectionRanges((params, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); const positions: Position[] = params.positions; diff --git a/extensions/css-language-features/server/src/languageModelCache.ts b/extensions/css-language-features/server/src/languageModelCache.ts index 561de4a9a7afa5a4a9b34de17e10ae75e8ec7d30..d069af0f7b6c409248e20e0a5d2664dd2d5a3d74 100644 --- a/extensions/css-language-features/server/src/languageModelCache.ts +++ b/extensions/css-language-features/server/src/languageModelCache.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-css-languageservice'; export interface LanguageModelCache { get(document: TextDocument): T; @@ -79,4 +79,4 @@ export function getLanguageModelCache(maxEntries: number, cleanupIntervalTime } } }; -} \ No newline at end of file +} diff --git a/extensions/css-language-features/server/src/node/cssServerMain.ts b/extensions/css-language-features/server/src/node/cssServerMain.ts index 9e145398ff15bd3a90b20b612fa3cd38a698adf9..501022dc34d693d4ca4bf5a2115293112c67cd88 100644 --- a/extensions/css-language-features/server/src/node/cssServerMain.ts +++ b/extensions/css-language-features/server/src/node/cssServerMain.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createConnection, Connection } from 'vscode-languageserver/node'; +import { createConnection, Connection, Disposable } from 'vscode-languageserver/node'; import { formatError } from '../utils/runner'; -import { startServer } from '../cssServer'; +import { RuntimeEnvironment, startServer } from '../cssServer'; import { getNodeFSRequestService } from './nodeFs'; // Create a connection for the server. @@ -18,4 +18,18 @@ process.on('unhandledRejection', (e: any) => { connection.console.error(formatError(`Unhandled exception`, e)); }); -startServer(connection, { file: getNodeFSRequestService() }); +const runtime: RuntimeEnvironment = { + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setImmediate(callback, ...args); + return { dispose: () => clearImmediate(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }, + file: getNodeFSRequestService() +}; + +startServer(connection, runtime); diff --git a/extensions/css-language-features/server/src/utils/runner.ts b/extensions/css-language-features/server/src/utils/runner.ts index 9b82baf54820d0ce79c7e9f2e18cbbfd5998e729..c01d5e0ec02334cd5c6cc8ae1f1fb864895e5a1c 100644 --- a/extensions/css-language-features/server/src/utils/runner.ts +++ b/extensions/css-language-features/server/src/utils/runner.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ResponseError, CancellationToken, LSPErrorCodes } from 'vscode-languageserver'; +import { RuntimeEnvironment } from '../cssServer'; export function formatError(message: string, err: any): string { if (err instanceof Error) { @@ -17,9 +18,9 @@ export function formatError(message: string, err: any): string { return message; } -export function runSafeAsync(func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { +export function runSafeAsync(runtime: RuntimeEnvironment, func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { return new Promise>((resolve) => { - setImmediate(() => { + runtime.timer.setImmediate(() => { if (token.isCancellationRequested) { resolve(cancelValue()); } diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 710f3d7be706376e05acf255850f4e5551898f3d..e48ed9160f8a1070a87b26ab9a192ef6196d61f0 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -30,7 +30,7 @@ "jsonc-parser": "^2.2.1", "markdown-it": "^12.0.4", "parse5": "^3.0.2", - "vscode-nls": "^4.1.1" + "vscode-nls": "^5.0.0" }, "contributes": { "jsonValidation": [ diff --git a/extensions/extension-editing/src/extensionEditingBrowserMain.ts b/extensions/extension-editing/src/extensionEditingBrowserMain.ts index 16f4d5e7d0bb9b761bfba2b0c5f07b0ee0e02701..f9d6885c6223c37cbda1296236ed22dec25c43c0 100644 --- a/extensions/extension-editing/src/extensionEditingBrowserMain.ts +++ b/extensions/extension-editing/src/extensionEditingBrowserMain.ts @@ -18,4 +18,5 @@ function registerPackageDocumentCompletions(): vscode.Disposable { return new PackageDocument(document).provideCompletionItems(position, token); } }); + } diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index 01c08624126a2dc4fc8f058f22fd04eb8df75257..ecb53673162a142703502a52904a63aad9226869 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -72,7 +72,7 @@ uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -vscode-nls@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" - integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index ec8b58deb85b1c3640a55be72f32c91a9d61573e..ed4df6794f1777255212df4708c2a2c29c766c11 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -88,7 +88,7 @@ "node-fetch": "2.6.1", "uuid": "8.1.0", "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.1.2", + "vscode-nls": "^5.0.0", "vscode-tas-client": "^0.1.22" }, "devDependencies": { diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index 090dc94dcb76ef7d33156483950e290bf1950b7c..4521016ca5ecf05c270679cb8357de0279e497c9 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -175,10 +175,10 @@ vscode-extension-telemetry@0.1.7: dependencies: applicationinsights "1.7.4" -vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== vscode-tas-client@^0.1.22: version "0.1.22" diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts index 425dfcd6609ec2a8345fabc08e7e2222ebe53bce..843bf79aeb28d2f43f15a3c8040b834d0f8c410e 100644 --- a/extensions/html-language-features/client/src/browser/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, Uri } from 'vscode'; +import { Disposable, ExtensionContext, Uri } from 'vscode'; import { LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../htmlClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -24,7 +24,14 @@ export function activate(context: ExtensionContext) { return new LanguageClient(id, name, clientOptions, worker); }; - startClient(context, newLanguageClient, { TextDecoder }); + const timer = { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }; + + startClient(context, newLanguageClient, { TextDecoder, timer }); } catch (e) { console.log(e); diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index af9ba15253bfefceac53fb9cc3d47c990fe6ffc1..8c809719b8584437cebc720d389f346d350bcc35 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -58,6 +58,9 @@ export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string; } }; fs?: RequestService; telemetry?: TelemetryReporter; + readonly timer: { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; + } } export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { @@ -126,7 +129,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); return client.sendRequest(TagCloseRequest.type, param); }; - disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true }, 'html.autoClosingTags'); + disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true }, 'html.autoClosingTags', runtime); toDispose.push(disposable); disposable = client.onTelemetry(e => { diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index 097bda1f6aab3cb7b2668fa57c36f8530ff8222e..d402ee31e79546de60863987459a88e823778290 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { getNodeFSRequestService } from './nodeFs'; -import { ExtensionContext } from 'vscode'; +import { Disposable, ExtensionContext } from 'vscode'; import { startClient, LanguageClientConstructor } from '../htmlClient'; import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; @@ -37,7 +37,14 @@ export function activate(context: ExtensionContext) { return new LanguageClient(id, name, serverOptions, clientOptions); }; - startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder, telemetry }); + const timer = { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }; + + startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder, telemetry, timer }); } interface IPackageInfo { diff --git a/extensions/html-language-features/client/src/tagClosing.ts b/extensions/html-language-features/client/src/tagClosing.ts index 298edcdaa0a524614971b5c6a439841a77d0ecd5..0b0b7b2114fba2a173ae88a9c69f454cf35810e4 100644 --- a/extensions/html-language-features/client/src/tagClosing.ts +++ b/extensions/html-language-features/client/src/tagClosing.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { window, workspace, Disposable, TextDocumentContentChangeEvent, TextDocument, Position, SnippetString } from 'vscode'; +import { Runtime } from './htmlClient'; -export function activateTagClosing(tagProvider: (document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, configName: string): Disposable { +export function activateTagClosing(tagProvider: (document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, configName: string, runtime: Runtime): Disposable { let disposables: Disposable[] = []; workspace.onDidChangeTextDocument(event => onDidChangeTextDocument(event.document, event.contentChanges), null, disposables); @@ -14,7 +15,13 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio updateEnabledState(); window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); - let timeout: NodeJS.Timer | undefined = undefined; + let timeout: Disposable | undefined = undefined; + + disposables.push({ + dispose: () => { + timeout?.dispose(); + } + }); function updateEnabledState() { isEnabled = false; @@ -40,8 +47,8 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio if (document !== activeDocument || changes.length === 0) { return; } - if (typeof timeout !== 'undefined') { - clearTimeout(timeout); + if (timeout) { + timeout.dispose(); } let lastChange = changes[changes.length - 1]; let lastCharacter = lastChange.text[lastChange.text.length - 1]; @@ -50,7 +57,7 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio } let rangeStart = lastChange.range.start; let version = document.version; - timeout = setTimeout(() => { + timeout = runtime.timer.setTimeout(() => { let position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length); tagProvider(document, position).then(text => { if (text && isEnabled) { @@ -72,4 +79,4 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio }, 100); } return Disposable.from(...disposables); -} \ No newline at end of file +} diff --git a/extensions/html-language-features/server/extension-browser.webpack.config.js b/extensions/html-language-features/server/extension-browser.webpack.config.js index ae024e8d7dd9b342dbf4c198cc937354a78c5439..7275f7c8b773764806a0ca89f0c94a4858228c1a 100644 --- a/extensions/html-language-features/server/extension-browser.webpack.config.js +++ b/extensions/html-language-features/server/extension-browser.webpack.config.js @@ -18,7 +18,8 @@ const serverConfig = withBrowserDefaults({ output: { filename: 'htmlServerMain.js', path: path.join(__dirname, 'dist', 'browser'), - libraryTarget: 'var' + libraryTarget: 'var', + library: 'serverExportVar' }, optimization: { splitChunks: { @@ -26,7 +27,7 @@ const serverConfig = withBrowserDefaults({ } } }); -serverConfig.module.noParse = /typescript[\/\\]lib[\/\\]typescript\.js/; +serverConfig.module.noParse = /typescript[\/\\]lib[\/\\]typescript\.js/; serverConfig.module.rules.push({ test: /javascriptLibs.ts$/, use: [ diff --git a/extensions/html-language-features/server/src/browser/htmlServerMain.ts b/extensions/html-language-features/server/src/browser/htmlServerMain.ts index 1d38d33db3787de75a760b4e962642486d93764f..cb41d3cf19c818e4a3a4f378424608349dd80c34 100644 --- a/extensions/html-language-features/server/src/browser/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/browser/htmlServerMain.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser'; -import { startServer } from '../htmlServer'; +import { createConnection, BrowserMessageReader, BrowserMessageWriter, Disposable } from 'vscode-languageserver/browser'; +import { RuntimeEnvironment, startServer } from '../htmlServer'; declare let self: any; @@ -13,4 +13,17 @@ const messageWriter = new BrowserMessageWriter(self); const connection = createConnection(messageReader, messageWriter); -startServer(connection, {}); +const runtime: RuntimeEnvironment = { + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setTimeout(callback, 0, ...args); + return { dispose: () => clearTimeout(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + } +}; + +startServer(connection, runtime); diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index e153cf7fa91f8030bbe6d3cf0851aec9cf68eb93..425e73c806aa2530c599524686a64cb405e8e4fe 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -50,6 +50,10 @@ export interface RuntimeEnvironment { file?: RequestService; http?: RequestService configureHttpRequests?(proxy: string, strictSSL: boolean): void; + readonly timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable; + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; + } } export function startServer(connection: Connection, runtime: RuntimeEnvironment) { @@ -215,7 +219,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } }); - const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; + const pendingValidationRequests: { [uri: string]: Disposable } = {}; const validationDelayMs = 500; // The content of a text document has changed. This event is emitted @@ -233,14 +237,14 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) function cleanPendingValidation(textDocument: TextDocument): void { const request = pendingValidationRequests[textDocument.uri]; if (request) { - clearTimeout(request); + request.dispose(); delete pendingValidationRequests[textDocument.uri]; } } function triggerValidation(textDocument: TextDocument): void { cleanPendingValidation(textDocument); - pendingValidationRequests[textDocument.uri] = setTimeout(() => { + pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => { delete pendingValidationRequests[textDocument.uri]; validateTextDocument(textDocument); }, validationDelayMs); @@ -277,7 +281,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } connection.onCompletion(async (textDocumentPosition, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(textDocumentPosition.textDocument.uri); if (!document) { return null; @@ -305,7 +309,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onCompletionResolve((item, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const data = item.data; if (data && data.languageId && data.uri) { const mode = languageModes.getMode(data.languageId); @@ -319,7 +323,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onHover((textDocumentPosition, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(textDocumentPosition.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); @@ -334,7 +338,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentHighlight((documentHighlightParams, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(documentHighlightParams.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, documentHighlightParams.position); @@ -347,7 +351,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDefinition((definitionParams, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(definitionParams.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, definitionParams.position); @@ -360,7 +364,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onReferences((referenceParams, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(referenceParams.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, referenceParams.position); @@ -373,7 +377,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onSignatureHelp((signatureHelpParms, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(signatureHelpParms.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, signatureHelpParms.position); @@ -401,15 +405,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } connection.onDocumentRangeFormatting((formatParams, token) => { - return runSafe(() => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); + return runSafe(runtime, () => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); }); connection.onDocumentFormatting((formatParams, token) => { - return runSafe(() => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token); + return runSafe(runtime, () => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token); }); connection.onDocumentLinks((documentLinkParam, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(documentLinkParam.textDocument.uri); const links: DocumentLink[] = []; if (document) { @@ -425,7 +429,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentSymbol((documentSymbolParms, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(documentSymbolParms.textDocument.uri); const symbols: SymbolInformation[] = []; if (document) { @@ -440,7 +444,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onRequest(DocumentColorRequest.type, (params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const infos: ColorInformation[] = []; const document = documents.get(params.textDocument.uri); if (document) { @@ -455,7 +459,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onRequest(ColorPresentationRequest.type, (params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { const mode = languageModes.getModeAtPosition(document, params.range.start); @@ -468,7 +472,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onRequest(TagCloseRequest.type, (params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { const pos = params.position; @@ -484,7 +488,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onFoldingRanges((params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { return getFoldingRanges(languageModes, document, foldingRangeLimit, token); @@ -494,7 +498,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onSelectionRanges((params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { return getSelectionRanges(languageModes, document, params.positions); @@ -504,7 +508,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onRenameRequest((params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); const position: Position = params.position; @@ -520,7 +524,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.languages.onLinkedEditingRange((params, token) => { - return /* todo remove when microsoft/vscode-languageserver-node#700 fixed */ runSafe(async () => { + return /* todo remove when microsoft/vscode-languageserver-node#700 fixed */ runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { const pos = params.position; @@ -547,7 +551,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } connection.onRequest(SemanticTokenRequest.type, (params, token) => { - return runSafe(async () => { + return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { return getSemanticTokenProvider().getSemanticTokens(document, params.ranges); @@ -557,7 +561,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onRequest(SemanticTokenLegendRequest.type, token => { - return runSafe(async () => { + return runSafe(runtime, async () => { return getSemanticTokenProvider().legend; }, null, `Error while computing semantic tokens legend`, token); }); diff --git a/extensions/html-language-features/server/src/node/htmlServerMain.ts b/extensions/html-language-features/server/src/node/htmlServerMain.ts index 759fde4088399d475ff60fb6142c0043a4848d04..faf82b3c40c13f8682c9a09e5e8e4043e032df9e 100644 --- a/extensions/html-language-features/server/src/node/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/node/htmlServerMain.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createConnection, Connection } from 'vscode-languageserver/node'; +import { createConnection, Connection, Disposable } from 'vscode-languageserver/node'; import { formatError } from '../utils/runner'; -import { startServer } from '../htmlServer'; +import { RuntimeEnvironment, startServer } from '../htmlServer'; import { getNodeFSRequestService } from './nodeFs'; @@ -19,5 +19,18 @@ process.on('unhandledRejection', (e: any) => { connection.console.error(formatError(`Unhandled exception`, e)); }); +const runtime: RuntimeEnvironment = { + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setImmediate(callback, ...args); + return { dispose: () => clearImmediate(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }, + file: getNodeFSRequestService() +}; -startServer(connection, { file: getNodeFSRequestService() }); +startServer(connection, runtime); diff --git a/extensions/html-language-features/server/src/utils/runner.ts b/extensions/html-language-features/server/src/utils/runner.ts index 7889b6a4216e846e2bc5cfb06e3bc629a11885ea..b1f86f6ef1397f7db54e2ca5494015f98793b7ba 100644 --- a/extensions/html-language-features/server/src/utils/runner.ts +++ b/extensions/html-language-features/server/src/utils/runner.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ResponseError, CancellationToken, LSPErrorCodes } from 'vscode-languageserver'; +import { RuntimeEnvironment } from '../htmlServer'; export function formatError(message: string, err: any): string { if (err instanceof Error) { @@ -17,9 +18,9 @@ export function formatError(message: string, err: any): string { return message; } -export function runSafe(func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { +export function runSafe(runtime: RuntimeEnvironment, func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { return new Promise>((resolve) => { - setImmediate(() => { + runtime.timer.setImmediate(() => { if (token.isCancellationRequested) { resolve(cancelValue()); } diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 844d2554a2ea36a82f2eb0dd2823b5ffd06ed36f..1e572e0cc8540de7ad458db0c755c66f554c7721 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -82,7 +82,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.0.0" }, "repository": { "type": "git", diff --git a/extensions/image-preview/yarn.lock b/extensions/image-preview/yarn.lock index f3206ddd72c08215d43af8318ea3b51bdbf58541..579c6a7c4a9546547aba61f25740b18623abe1ba 100644 --- a/extensions/image-preview/yarn.lock +++ b/extensions/image-preview/yarn.lock @@ -90,7 +90,7 @@ vscode-extension-telemetry@0.1.7: dependencies: applicationinsights "1.7.4" -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/json-language-features/extension.webpack.config.js b/extensions/json-language-features/extension.webpack.config.js index 13a0595299561598844eb91b98e2eb856ab56ef6..20b8550c447ff341f7c92332901d5ab516c00612 100644 --- a/extensions/json-language-features/extension.webpack.config.js +++ b/extensions/json-language-features/extension.webpack.config.js @@ -9,7 +9,6 @@ const withDefaults = require('../shared.webpack.config'); const path = require('path'); -const webpack = require('webpack'); const config = withDefaults({ context: path.join(__dirname, 'client'), @@ -22,7 +21,5 @@ const config = withDefaults({ } }); -// add plugin, don't replace inherited -config.plugins.push(new webpack.IgnorePlugin(/vertx/)); // request-light dependency module.exports = config; diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 4b832e4ebb6f46407e1ef07712259b3a8223cd69..4d9ce21e052f8e29a1b09b168fa7d07dc41b7e0f 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -134,7 +134,7 @@ ] }, "dependencies": { - "request-light": "^0.4.0", + "request-light": "^0.5.3", "vscode-extension-telemetry": "0.1.7", "vscode-languageclient": "^7.0.0", "vscode-nls": "^5.0.0" diff --git a/extensions/json-language-features/server/extension-browser.webpack.config.js b/extensions/json-language-features/server/extension-browser.webpack.config.js index b1ae74c7b606088fb19235309ed1f6a50973fb3b..0d270258a87b4a83230e00786ec979d56fdd34f5 100644 --- a/extensions/json-language-features/server/extension-browser.webpack.config.js +++ b/extensions/json-language-features/server/extension-browser.webpack.config.js @@ -18,6 +18,7 @@ module.exports = withBrowserDefaults({ output: { filename: 'jsonServerMain.js', path: path.join(__dirname, 'dist', 'browser'), - libraryTarget: 'var' + libraryTarget: 'var', + library: 'serverExportVar' } }); diff --git a/extensions/json-language-features/server/extension.webpack.config.js b/extensions/json-language-features/server/extension.webpack.config.js index 50a7c3d545f00d9a481154da0e0332285f4559fb..8341493bc2f6d487d99bed983c289098e2352248 100644 --- a/extensions/json-language-features/server/extension.webpack.config.js +++ b/extensions/json-language-features/server/extension.webpack.config.js @@ -9,7 +9,6 @@ const withDefaults = require('../../shared.webpack.config'); const path = require('path'); -const webpack = require('webpack'); const config = withDefaults({ context: path.join(__dirname), @@ -22,7 +21,4 @@ const config = withDefaults({ } }); -// add plugin, don't replace inherited -config.plugins.push(new webpack.IgnorePlugin(/vertx/)); // request-light dependency - module.exports = config; diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index ac81fa69225fee30febce857a62ce1e46f0db2cc..fb1923f065618c25204e92c843f31473f3704a43 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -13,7 +13,7 @@ "main": "./out/node/jsonServerMain", "dependencies": { "jsonc-parser": "^3.0.0", - "request-light": "^0.4.0", + "request-light": "^0.5.3", "vscode-json-languageservice": "^4.1.5", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" diff --git a/extensions/json-language-features/server/src/browser/jsonServerMain.ts b/extensions/json-language-features/server/src/browser/jsonServerMain.ts index 5394412877c81cd5b23fb995695b10436cb4ce71..689521d26b0a91f9bab5d7352d336b8ee2e7cfb5 100644 --- a/extensions/json-language-features/server/src/browser/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/browser/jsonServerMain.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser'; -import { startServer } from '../jsonServer'; +import { createConnection, BrowserMessageReader, BrowserMessageWriter, Disposable } from 'vscode-languageserver/browser'; +import { RuntimeEnvironment, startServer } from '../jsonServer'; declare let self: any; @@ -13,4 +13,17 @@ const messageWriter = new BrowserMessageWriter(self); const connection = createConnection(messageReader, messageWriter); -startServer(connection, {}); +const runtime: RuntimeEnvironment = { + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setTimeout(callback, 0, ...args); + return { dispose: () => clearTimeout(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + } +}; + +startServer(connection, runtime); diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index bb3538a0c985dac33d3ff262354dc301ef407da9..2fcaa0a82e627f95d425fe85eb3b8e757309034e 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -48,6 +48,10 @@ export interface RuntimeEnvironment { file?: RequestService; http?: RequestService configureHttpRequests?(proxy: string, strictSSL: boolean): void; + readonly timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable; + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; + } } export function startServer(connection: Connection, runtime: RuntimeEnvironment) { @@ -171,13 +175,19 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const limitExceededWarnings = function () { - const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: NodeJS.Timeout; } } = {}; + const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: Disposable; } } = {}; + + const showLimitedNotification = (uri: string, resultLimit: number) => { + const warning = pendingWarnings[uri]; + connection.sendNotification(ResultLimitReachedNotification.type, `${basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); + warning.timeout = undefined; + }; return { cancel(uri: string) { const warning = pendingWarnings[uri]; if (warning && warning.timeout) { - clearTimeout(warning.timeout); + warning.timeout.dispose(); delete pendingWarnings[uri]; } }, @@ -191,13 +201,11 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return; } warning.features[name] = name; - warning.timeout.refresh(); + warning.timeout.dispose(); + warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000); } else { warning = { features: { [name]: name } }; - warning.timeout = setTimeout(() => { - connection.sendNotification(ResultLimitReachedNotification.type, `${basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); - warning.timeout = undefined; - }, 2000); + warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000); pendingWarnings[uri] = warning; } }; @@ -316,20 +324,20 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); }); - const pendingValidationRequests: { [uri: string]: NodeJS.Timer; } = {}; + const pendingValidationRequests: { [uri: string]: Disposable; } = {}; const validationDelayMs = 300; function cleanPendingValidation(textDocument: TextDocument): void { const request = pendingValidationRequests[textDocument.uri]; if (request) { - clearTimeout(request); + request.dispose(); delete pendingValidationRequests[textDocument.uri]; } } function triggerValidation(textDocument: TextDocument): void { cleanPendingValidation(textDocument); - pendingValidationRequests[textDocument.uri] = setTimeout(() => { + pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => { delete pendingValidationRequests[textDocument.uri]; validateTextDocument(textDocument); }, validationDelayMs); @@ -351,7 +359,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const documentSettings: DocumentLanguageSettings = textDocument.languageId === 'jsonc' ? { comments: 'ignore', trailingCommas: 'warning' } : { comments: 'error', trailingCommas: 'error' }; languageService.doValidation(textDocument, jsonDocument, documentSettings).then(diagnostics => { - setImmediate(() => { + runtime.timer.setImmediate(() => { const currDocument = documents.get(textDocument.uri); if (currDocument && currDocument.version === version) { respond(diagnostics); // Send the computed diagnostics to VSCode. @@ -388,7 +396,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } connection.onCompletion((textDocumentPosition, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(textDocumentPosition.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); @@ -399,7 +407,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onHover((textDocumentPositionParams, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(textDocumentPositionParams.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); @@ -410,7 +418,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentSymbol((documentSymbolParams, token) => { - return runSafe(() => { + return runSafe(runtime, () => { const document = documents.get(documentSymbolParams.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); @@ -439,15 +447,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } connection.onDocumentRangeFormatting((formatParams, token) => { - return runSafe(() => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); + return runSafe(runtime, () => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); }); connection.onDocumentFormatting((formatParams, token) => { - return runSafe(() => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token); + return runSafe(runtime, () => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token); }); connection.onDocumentColor((params, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document colors'); @@ -459,7 +467,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onColorPresentation((params, token) => { - return runSafe(() => { + return runSafe(runtime, () => { const document = documents.get(params.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); @@ -470,7 +478,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onFoldingRanges((params, token) => { - return runSafe(() => { + return runSafe(runtime, () => { const document = documents.get(params.textDocument.uri); if (document) { const onRangeLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, foldingRangeLimit, 'folding ranges'); @@ -482,7 +490,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.onSelectionRanges((params, token) => { - return runSafe(() => { + return runSafe(runtime, () => { const document = documents.get(params.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); @@ -493,7 +501,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onDocumentLinks((params, token) => { - return runSafeAsync(async () => { + return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); diff --git a/extensions/json-language-features/server/src/node/jsonServerMain.ts b/extensions/json-language-features/server/src/node/jsonServerMain.ts index 94192b4b1ce4892bdebf8123c51396bdba2f5c5e..17e0fcd99401b672c3d805e666b080b91c70c60b 100644 --- a/extensions/json-language-features/server/src/node/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/node/jsonServerMain.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createConnection, Connection } from 'vscode-languageserver/node'; +import { createConnection, Connection, Disposable } from 'vscode-languageserver/node'; import { formatError } from '../utils/runner'; -import { startServer } from '../jsonServer'; +import { RuntimeEnvironment, startServer } from '../jsonServer'; import { RequestService } from '../requests'; import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light'; @@ -51,5 +51,22 @@ function getFileRequestService(): RequestService { }; } +const runtime: RuntimeEnvironment = { + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setImmediate(callback, ...args); + return { dispose: () => clearImmediate(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }, + file: getFileRequestService(), + http: getHTTPRequestService(), + configureHttpRequests +}; + + -startServer(connection, { file: getFileRequestService(), http: getHTTPRequestService(), configureHttpRequests }); +startServer(connection, runtime); diff --git a/extensions/json-language-features/server/src/utils/runner.ts b/extensions/json-language-features/server/src/utils/runner.ts index 968df7987ad543af22a31f34f197dad2cd0fe510..0ad24dd02dc82b80457b37b0c610321e21b25d8b 100644 --- a/extensions/json-language-features/server/src/utils/runner.ts +++ b/extensions/json-language-features/server/src/utils/runner.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken, ResponseError, LSPErrorCodes } from 'vscode-languageserver'; +import { RuntimeEnvironment } from '../jsonServer'; export function formatError(message: string, err: any): string { if (err instanceof Error) { @@ -17,9 +18,9 @@ export function formatError(message: string, err: any): string { return message; } -export function runSafeAsync(func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { +export function runSafeAsync(runtime: RuntimeEnvironment, func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { return new Promise>((resolve) => { - setImmediate(() => { + runtime.timer.setImmediate(() => { if (token.isCancellationRequested) { resolve(cancelValue()); } @@ -38,9 +39,9 @@ export function runSafeAsync(func: () => Thenable, errorVal: T, errorMessa }); } -export function runSafe(func: () => T, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { +export function runSafe(runtime: RuntimeEnvironment, func: () => T, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { return new Promise>((resolve) => { - setImmediate(() => { + runtime.timer.setImmediate(() => { if (token.isCancellationRequested) { resolve(cancelValue()); } else { diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 7642cc7b168dc12cac87ee047ee8a7d83fd79ac0..c703005b6e3e478f327f1d9831649059cb0fe3db 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -12,20 +12,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -agent-base@4: - version "4.1.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.1.2.tgz#80fa6cde440f4dcf9af2617cf246099b5d99f0c8" - integrity sha512-VE6QoEdaugY86BohRtfGmTDabxdU5sCKOkbcPA6PXKJsRzEi/7A3RCTxJal1ft/4qSfPht5/iQLhMh/wzSkkNw== - dependencies: - es6-promisify "^5.0.0" - -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -44,41 +30,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -debug@3.1.0, debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -es6-promise@^4.0.3: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" - integrity sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -https-proxy-agent@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - jsonc-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" @@ -91,19 +42,10 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -request-light@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.4.0.tgz#c6b91ef00b18cb0de75d2127e55b3a2c9f7f90f9" - integrity sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.4" - vscode-nls "^4.1.2" +request-light@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.3.tgz#bc5b103b939cde7c0653fff8ab4f43440779a5fb" + integrity sha512-wlHI5WbSZ2PcoLW2qE616PKDFCFJWaJjgRg2VaNXF9pa8MJ7+dKveJzw2HhJF8Et5uOmh9nAUJUX4iFI3Mvm/g== vscode-json-languageservice@^4.1.5: version "4.1.5" @@ -147,11 +89,6 @@ vscode-languageserver@^7.0.0: dependencies: vscode-languageserver-protocol "3.16.0" -vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== - vscode-nls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 4ba9b7b63016367f3ba5af26ad37a0ca6816cb40..b37678d852361a79328812bff3183e993b61414f 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -7,20 +7,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -agent-base@4: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - applicationinsights@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.7.4.tgz#e7d96435594d893b00cf49f70a5927105dbb8749" @@ -81,20 +67,6 @@ continuation-local-storage@^3.2.1: async-listener "^0.6.0" emitter-listener "^1.1.1" -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - diagnostic-channel-publishers@^0.3.3: version "0.3.5" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" @@ -114,34 +86,6 @@ emitter-listener@^1.0.1, emitter-listener@^1.1.1: dependencies: shimmer "^1.2.0" -es6-promise@^4.0.3: - version "4.2.6" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" - integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -https-proxy-agent@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -156,24 +100,10 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -request-light@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.4.0.tgz#c6b91ef00b18cb0de75d2127e55b3a2c9f7f90f9" - integrity sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.4" - vscode-nls "^4.1.2" +request-light@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.3.tgz#bc5b103b939cde7c0653fff8ab4f43440779a5fb" + integrity sha512-wlHI5WbSZ2PcoLW2qE616PKDFCFJWaJjgRg2VaNXF9pa8MJ7+dKveJzw2HhJF8Et5uOmh9nAUJUX4iFI3Mvm/g== semver@^5.3.0: version "5.5.0" @@ -236,11 +166,6 @@ vscode-languageserver-types@3.16.0: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== -vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== - vscode-nls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 61d2a848089d4f9cfc656d826c6c6778f4f1d88b..4e13ef4b9f163c90cc588c96c46f3e9c9571011c 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -356,7 +356,7 @@ "markdown-it": "^12.0.3", "markdown-it-front-matter": "^0.2.1", "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/highlight.js": "10.1.0", diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 0f16361338b5f60af351b7a190d275dbf6e1fb11..7f1ea512f761c0581fa54c59a5e009c7bd9a009f 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -152,7 +152,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } this._register(_contributionProvider.onContributionsChanged(() => { - setImmediate(() => this.refresh()); + setTimeout(() => this.refresh(), 0); })); this._register(vscode.workspace.onDidChangeTextDocument(event => { diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 3f1174c3684fe6e97517773828e2db7c655422ac..c5a5632b09a15e02c69d497185132bddb3ef733a 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -182,7 +182,7 @@ vscode-extension-telemetry@0.1.7: dependencies: applicationinsights "1.7.4" -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index 2c4719f4b71a7353cb158b19a9be07e531422859..942abb0ce9fef29c10d5d5b7ee7d0d36782e6771 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -156,7 +156,7 @@ } }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/node": "14.x" diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock index f7a30098ef45d419ba06ce3009ceb4fc03f7300d..ede1d9c77362dd3304f454486456e2190fe52f76 100644 --- a/extensions/merge-conflict/yarn.lock +++ b/extensions/merge-conflict/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/microsoft-authentication/extension.webpack.config.js b/extensions/microsoft-authentication/extension.webpack.config.js index e18229a8e170acd46eebcf07542aff73db4d77b5..a513ac5c3b51134122100c16a57837fef56b8790 100644 --- a/extensions/microsoft-authentication/extension.webpack.config.js +++ b/extensions/microsoft-authentication/extension.webpack.config.js @@ -7,7 +7,6 @@ 'use strict'; -const path = require('path'); const withDefaults = require('../shared.webpack.config'); module.exports = withDefaults({ diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 148b5c3625c0565895a82a91fa728b8f84441f91..388466b9733a84848b8db49d5670ad4eedb00f2f 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -60,7 +60,7 @@ "stream": "0.0.2", "uuid": "^8.2.0", "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.1.1" + "vscode-nls": "^5.0.0" }, "repository": { "type": "git", diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index 54025e55a213baf78be6d59a50b3cc0040d70496..1d8476af9ed3cdbf4507d549decb01cf7de0dc79 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -229,7 +229,7 @@ vscode-extension-telemetry@0.1.7: dependencies: applicationinsights "1.7.4" -vscode-nls@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" - integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/npm/extension-browser.webpack.config.js b/extensions/npm/extension-browser.webpack.config.js index b9f00ed50bda7749fc3b3454937a326699b96921..ec1313ebf260d574a50f2b904437184da398c7f3 100644 --- a/extensions/npm/extension-browser.webpack.config.js +++ b/extensions/npm/extension-browser.webpack.config.js @@ -9,7 +9,7 @@ const withBrowserDefaults = require('../shared.webpack.config').browser; -module.exports = withBrowserDefaults({ +const config = withBrowserDefaults({ context: __dirname, entry: { extension: './src/npmBrowserMain.ts' @@ -17,8 +17,11 @@ module.exports = withBrowserDefaults({ output: { filename: 'npmBrowserMain.js' }, - node: { - 'child_process': 'empty', - 'which': 'empty' + resolve: { + fallback: { + 'child_process': false + } } }); + +module.exports = config; diff --git a/extensions/npm/extension.webpack.config.js b/extensions/npm/extension.webpack.config.js index 1c6d9493e3340cee21d8e7451c4b0d08517c9c8d..320956abe3d095f5f248bd366d23858eba1ac949 100644 --- a/extensions/npm/extension.webpack.config.js +++ b/extensions/npm/extension.webpack.config.js @@ -7,8 +7,6 @@ 'use strict'; -const path = require('path'); - const withDefaults = require('../shared.webpack.config'); module.exports = withDefaults({ diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 038f091b78e820e85fdc0c204898d1cfacdb1fa6..2828df0655e4767e7be031e3b00076aa0187ea48 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -22,8 +22,8 @@ "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^2.2.1", "minimatch": "^3.0.4", - "request-light": "^0.4.0", - "vscode-nls": "^4.1.1", + "request-light": "^0.5.3", + "vscode-nls": "^5.0.0", "which": "^2.0.2", "which-pm": "^2.0.0" }, diff --git a/extensions/npm/src/features/bowerJSONContribution.ts b/extensions/npm/src/features/bowerJSONContribution.ts index e02fd11ac8c779901c158c45e24620e73706a5c0..11b4d7f74dd9dff26e505e00f44c03dd4e7b08c0 100644 --- a/extensions/npm/src/features/bowerJSONContribution.ts +++ b/extensions/npm/src/features/bowerJSONContribution.ts @@ -63,7 +63,7 @@ export class BowerJSONContribution implements IJSONContribution { return this.xhr({ url: queryUrl, - agent: USER_AGENT + headers: { agent: USER_AGENT } }).then((success) => { if (success.status === 200) { try { @@ -165,7 +165,7 @@ export class BowerJSONContribution implements IJSONContribution { return this.xhr({ url: queryUrl, - agent: USER_AGENT + headers: { agent: USER_AGENT } }).then((success) => { try { const obj = JSON.parse(success.responseText); diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index 378f51b0b7d66136ac72fe2957167fe35a096dab..23d7b4678b86930346f7701a085b03cf0da68449 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -96,7 +96,7 @@ export class PackageJSONContribution implements IJSONContribution { queryUrl = `https://registry.npmjs.org/-/v1/search?size=${LIMIT}&text=${encodeURIComponent(currentWord)}`; return this.xhr({ url: queryUrl, - agent: USER_AGENT + headers: { agent: USER_AGENT } }).then((success) => { if (success.status === 200) { try { @@ -156,7 +156,7 @@ export class PackageJSONContribution implements IJSONContribution { let queryUrl = `https://registry.npmjs.com/-/v1/search?text=scope:${scope}%20${name}&size=250`; return this.xhr({ url: queryUrl, - agent: USER_AGENT + headers: { agent: USER_AGENT } }).then((success) => { if (success.status === 200) { try { @@ -311,7 +311,7 @@ export class PackageJSONContribution implements IJSONContribution { try { const success = await this.xhr({ url: queryUrl, - agent: USER_AGENT + headers: { agent: USER_AGENT } }); const obj = JSON.parse(success.responseText); return { diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index 29712f0dcc0c894c8e624b20ffa181685f80263c..0fd8c73ea53d0777d38d15afdaad30b606a23258 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -17,20 +17,6 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.0.tgz#446d35586611dee657120de8e0457382a658fc25" integrity sha512-JHTNOEpZnACQdsTojWggn+SQ8IucfqEhtz7g8Z0G67WdSj4x3F0X5I2c/CVcl8z/QukGrIHeQ/N49v1au74XFQ== -agent-base@4: - version "4.2.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" - integrity sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - argparse@1.0.9, argparse@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" @@ -63,25 +49,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -debug@3.1.0, debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -es6-promise@^4.0.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" - integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -114,22 +81,6 @@ graceful-fs@^4.1.5: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -https-proxy-agent@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -185,11 +136,6 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -224,14 +170,10 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -request-light@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.4.0.tgz#c6b91ef00b18cb0de75d2127e55b3a2c9f7f90f9" - integrity sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.4" - vscode-nls "^4.1.2" +request-light@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.3.tgz#bc5b103b939cde7c0653fff8ab4f43440779a5fb" + integrity sha512-wlHI5WbSZ2PcoLW2qE616PKDFCFJWaJjgRg2VaNXF9pa8MJ7+dKveJzw2HhJF8Et5uOmh9nAUJUX4iFI3Mvm/g== sprintf-js@~1.0.2: version "1.0.3" @@ -250,15 +192,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-nls@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" - integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== - -vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== which-pm@^2.0.0: version "2.0.0" diff --git a/extensions/php/snippets/php.code-snippets b/extensions/php/snippets/php.code-snippets index b2e9078d91f3433f24851575f420d6f025ee7f9a..9c061291647d1085b54a2dd400ccecd9f7dde2ec 100644 --- a/extensions/php/snippets/php.code-snippets +++ b/extensions/php/snippets/php.code-snippets @@ -64,7 +64,7 @@ "body": [ "* @param ${1:Type} ${2:var} ${3:Description}$0" ], - "description": "Paramater documentation" + "description": "Parameter documentation" }, "function …": { "prefix": "fun", diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index ff8ee7e277536eb47f3e731513f229cafaeb271e..73e5036c7fb84f4d16b6991336a0c8401342ce75 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -16,12 +16,6 @@ const { NLSBundlePlugin } = require('vscode-nls-dev/lib/webpack-bundler'); const { DefinePlugin } = require('webpack'); function withNodeDefaults(/**@type WebpackConfig*/extConfig) { - // Need to find the top-most `package.json` file - const folderName = path.relative(__dirname, extConfig.context).split(/[\\\/]/)[0]; - const pkgPath = path.join(__dirname, folderName, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - const id = `${pkg.publisher}.${pkg.name}`; - /** @type WebpackConfig */ let defaultConfig = { mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') @@ -70,19 +64,28 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) { }, // yes, really source maps devtool: 'source-map', - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } - ] - }), - new NLSBundlePlugin(id) - ], + plugins: nodePlugins(extConfig.context), }; return merge(defaultConfig, extConfig); } +function nodePlugins(context) { + // Need to find the top-most `package.json` file + const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; + const pkgPath = path.join(__dirname, folderName, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + const id = `${pkg.publisher}.${pkg.name}`; + return [ + new CopyWebpackPlugin({ + patterns: [ + { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } + ] + }), + new NLSBundlePlugin(id) + ]; +} + function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { /** @type WebpackConfig */ @@ -93,8 +96,11 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { mainFields: ['module', 'main'], extensions: ['.ts', '.js'], // support ts-files and js-files alias: { - 'vscode-nls': path.resolve(__dirname, '../build/polyfills/vscode-nls.js'), 'vscode-extension-telemetry': path.resolve(__dirname, '../build/polyfills/vscode-extension-telemetry.js') + }, + fallback: { + 'path': require.resolve('path-browserify'), + 'util': require.resolve('util') } }, module: { @@ -130,21 +136,30 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig) { }, // yes, really source maps devtool: 'source-map', - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } - ] - }), - new DefinePlugin({ WEBWORKER: JSON.stringify(true) }) - ] + plugins: browserPlugins }; return merge(defaultConfig, extConfig); } +const browserPlugins = [ + new CopyWebpackPlugin({ + patterns: [ + { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } + ] + }), + new DefinePlugin({ + 'process.env': JSON.stringify({}), + 'process.env.BROWSER_ENV': JSON.stringify('true') + }) +]; + + + module.exports = withNodeDefaults; module.exports.node = withNodeDefaults; module.exports.browser = withBrowserDefaults; +module.exports.nodePlugins = nodePlugins; +module.exports.browserPlugins = browserPlugins; diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index f2af986a046ca8642a7acc1c08fd772ec380870d..2d7228605d58fbdd15722cb0e8c4a03ddb053585 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -67,7 +67,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/node": "14.x", diff --git a/extensions/simple-browser/yarn.lock b/extensions/simple-browser/yarn.lock index eb52a2a4a63fbd7d702d6febb9e62a8fe969e8b3..b473793395f971f0bf16bb3681f82afa853aae59 100644 --- a/extensions/simple-browser/yarn.lock +++ b/extensions/simple-browser/yarn.lock @@ -100,7 +100,7 @@ vscode-extension-telemetry@0.1.7: dependencies: applicationinsights "1.7.4" -vscode-nls@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/tsconfig.base.json b/extensions/tsconfig.base.json index 5bf396d4d39e55ad6360443914d00709b53fa471..2e4a6009a02981c3798955433cd2ddf91bf9bf2c 100644 --- a/extensions/tsconfig.base.json +++ b/extensions/tsconfig.base.json @@ -1,8 +1,29 @@ { "compilerOptions": { - "target": "es2019", + "target": "es2020", "lib": [ - "es2019" + "ES2016", + "ES2017.Object", + "ES2017.String", + "ES2017.Intl", + "ES2017.TypedArrays", + "ES2018.AsyncIterable", + "ES2018.AsyncGenerator", + "ES2018.Promise", + "ES2018.Regexp", + "ES2018.Intl", + "ES2019.Array", + "ES2019.Object", + "ES2019.String", + "ES2019.Symbol", + "ES2020.BigInt", + "ES2020.Promise", + "ES2020.String", + "ES2020.Symbol.WellKnown", + "ES2020.Intl", + "ES2021.Promise", + "ES2021.String", + "ES2021.WeakRef" ], "module": "commonjs", "strict": true, diff --git a/extensions/typescript-language-features/extension-browser.webpack.config.js b/extensions/typescript-language-features/extension-browser.webpack.config.js index f358adc77b9bb2206fa566624bf0acb385f9a652..e7ad156bf94ba7320db8668c218a2a7f186a0270 100644 --- a/extensions/typescript-language-features/extension-browser.webpack.config.js +++ b/extensions/typescript-language-features/extension-browser.webpack.config.js @@ -7,10 +7,11 @@ 'use strict'; const CopyPlugin = require('copy-webpack-plugin'); -const { lchmod } = require('graceful-fs'); const Terser = require('terser'); -const withBrowserDefaults = require('../shared.webpack.config').browser; +const defaultConfig = require('../shared.webpack.config'); +const withBrowserDefaults = defaultConfig.browser; +const browserPlugins = defaultConfig.browserPlugins; const languages = [ 'zh-tw', @@ -34,6 +35,8 @@ module.exports = withBrowserDefaults({ extension: './src/extension.browser.ts', }, plugins: [ + ...browserPlugins, // add plugins, don't replace inherited + // @ts-ignore new CopyPlugin({ patterns: [ @@ -62,7 +65,7 @@ module.exports = withBrowserDefaults({ from: '../node_modules/typescript/lib/tsserver.js', to: 'typescript/tsserver.web.js', transform: (content) => { - return Terser.minify(content.toString()).code; + return Terser.minify(content.toString()).then(output => output.code); }, transformPath: (targetPath) => { diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index f8f974df25b0870f4477a39a8a81ac7400bdddee..cb2c36943b62c4ab6e75f22aece9449c3969298e 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -35,7 +35,7 @@ "semver": "5.5.1", "typescript-vscode-sh-plugin": "^0.7.3", "vscode-extension-telemetry": "0.1.7", - "vscode-nls": "^4.1.1" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/node": "14.x", diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index edc458d994511bf6ff146d9befe92abd2abf6987..eb5acfea3078dec5702d9678ce44e39007474f46 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -8,7 +8,7 @@ import type * as Proto from '../protocol'; import { ITypeScriptServiceClient, ClientCapability } from '../typescriptService'; import API from '../utils/api'; import { coalesce } from '../utils/arrays'; -import { Delayer } from '../utils/async'; +import { Delayer, setImmediate } from '../utils/async'; import { nulToken } from '../utils/cancellation'; import { Disposable } from '../utils/dispose'; import * as languageModeIds from '../utils/languageModeIds'; diff --git a/extensions/typescript-language-features/src/tsServer/versionManager.ts b/extensions/typescript-language-features/src/tsServer/versionManager.ts index bb06e4505e2782df51ef32689f68cd2f1144ac79..2f2ee11152f5704558d34fc2abf9ceab80570433 100644 --- a/extensions/typescript-language-features/src/tsServer/versionManager.ts +++ b/extensions/typescript-language-features/src/tsServer/versionManager.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { TypeScriptServiceConfiguration } from '../utils/configuration'; +import { setImmediate } from '../utils/async'; import { Disposable } from '../utils/dispose'; import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider'; diff --git a/extensions/typescript-language-features/src/utils/async.ts b/extensions/typescript-language-features/src/utils/async.ts index a43cd07cbb4a2163d7696520393ac3071a85c3f0..3ec25abee2af1736c48f879072ec199b7e785fb1 100644 --- a/extensions/typescript-language-features/src/utils/async.ts +++ b/extensions/typescript-language-features/src/utils/async.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from 'vscode'; + export interface ITask { (): T; } @@ -60,3 +62,13 @@ export class Delayer { } } } + +export function setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + if (global.setImmediate) { + const handle = global.setImmediate(callback, ...args); + return { dispose: () => global.clearImmediate(handle) }; + } else { + const handle = setTimeout(callback, 0, ...args); + return { dispose: () => clearTimeout(handle) }; + } +} diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index a94f34e248ee48924ab14f312c0f4d9955a4b623..9e904fe12335049ef10bfaa8a6beb4993d37f218 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -110,7 +110,7 @@ vscode-extension-telemetry@0.1.7: dependencies: applicationinsights "1.7.4" -vscode-nls@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" + integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index d2298c1dabe804da9f487386df947a087c1aad0f..bc72fe0d18e8e8b0e0573ff6fa624f6e61d4a907 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -676,12 +676,14 @@ import { assertNoRpc } from '../utils'; 'b1~b2~', '~c2~c1' ]; + let data = ''; disposables.push(window.onDidWriteTerminalData(e => { if (terminal !== e.terminal) { return; } + data += sanitizeData(e.data); // Multiple expected could show up in the same data event - while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) { expectedText.shift(); // Check if all string are found, if so finish the test if (expectedText.length === 0) { @@ -719,12 +721,14 @@ import { assertNoRpc } from '../utils'; '~b2~', '~c2~' ]; + let data = ''; disposables.push(window.onDidWriteTerminalData(e => { if (terminal !== e.terminal) { return; } + data += sanitizeData(e.data); // Multiple expected could show up in the same data event - while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) { expectedText.shift(); // Check if all string are found, if so finish the test if (expectedText.length === 0) { @@ -761,12 +765,14 @@ import { assertNoRpc } from '../utils'; '~a1~', '~b1~' ]; + let data = ''; disposables.push(window.onDidWriteTerminalData(e => { if (terminal !== e.terminal) { return; } + data += sanitizeData(e.data); // Multiple expected could show up in the same data event - while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) { expectedText.shift(); // Check if all string are found, if so finish the test if (expectedText.length === 0) { @@ -800,12 +806,14 @@ import { assertNoRpc } from '../utils'; '~a1~', '~b2~' ]; + let data = ''; disposables.push(window.onDidWriteTerminalData(e => { if (terminal !== e.terminal) { return; } + data += sanitizeData(e.data); // Multiple expected could show up in the same data event - while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + while (expectedText.length > 0 && data.indexOf(expectedText[0]) >= 0) { expectedText.shift(); // Check if all string are found, if so finish the test if (expectedText.length === 0) { @@ -857,3 +865,15 @@ import { assertNoRpc } from '../utils'; }); }); }); + +function sanitizeData(data: string): string { + // Strip NL/CR so terminal dimensions don't impact tests + data = data.replaceAll(/[\r\n]/g, ''); + + // Strip escape sequences so winpty/conpty doesn't cause flakiness, do for all platforms for + // consistency + const terminalCodesRegex = /(?:\u001B|\u009B)[\[\]()#;?]*(?:(?:(?:[a-zA-Z0-9]*(?:;[a-zA-Z0-9]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-PR-TZcf-ntqry=><~]))/g; + data = data.replaceAll(terminalCodesRegex, ''); + + return data; +} diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index 3679062018fedc510db54b19456654945d0880fc..3f38da006c90b9caa2a46453f7af47c09afe10b0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event, workspace, ConfigurationTarget, TaskProcessStartEvent } from 'vscode'; +import { window, tasks, Disposable, TaskDefinition, Task, Task2, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event, workspace, ConfigurationTarget, TaskProcessStartEvent } from 'vscode'; import { assertNoRpc } from '../utils'; // Disable tasks tests: @@ -331,6 +331,38 @@ import { assertNoRpc } from '../utils'; } }); }); + + test('A task can be fetched with default task group information', () => { + return new Promise(async (resolve, reject) => { + // Add default to tasks.json since this is not possible using an API yet. + const tasksConfig = workspace.getConfiguration('tasks'); + await tasksConfig.update('version', '2.0.0', ConfigurationTarget.Workspace); + await tasksConfig.update('tasks', [ + { + label: 'Run this task', + type: 'shell', + command: 'sleep 1', + problemMatcher: [], + group: { + kind: 'build', + isDefault: 'true' + } + } + ], ConfigurationTarget.Workspace); + + const task = (await tasks.fetchTasks()); + + if (task && task.length > 0) { + const grp = task[0].group; + assert.strictEqual(grp?.isDefault, true); + resolve(); + } else { + reject('fetched task can\'t be undefined'); + } + // Reset tasks.json + await tasksConfig.update('tasks', []); + }); + }); }); }); }); diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index fc7503a482b29f83aa9b2840a2f19332e08d0c28..1ef92e2cf99ca1702bfbc4a49d64b8f45e72192b 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -62,6 +62,11 @@ "editor.insertSpaces": true, "editor.tabSize": 2, "editor.autoIndent": "advanced" + }, + "[dockercompose]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced" } } }, diff --git a/package.json b/package.json index 8055c59769bd2b142ee4ef056aff84dbba6c8892..375685fc2bb96d77fe912c00481e82dbd9b9eb21 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.59.0", - "distro": "b10247fd6dfcf2e8ff7fb4772bc58a2e97e108a3", + "distro": "ed2fca765acb42a6c17b1d023464f0c69077ca1b", "author": { "name": "Microsoft Corporation" }, @@ -127,7 +127,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "12.0.13", + "electron": "13.1.6", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", @@ -179,6 +179,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", + "path-browserify": "^1.0.1", "playwright": "1.12.3", "pump": "^1.0.1", "queue": "3.0.6", @@ -190,19 +191,20 @@ "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^1.0.0", - "ts-loader": "^6.2.1", + "ts-loader": "^9.2.3", "tsec": "0.1.4", - "typescript": "^4.4.0-dev.20210708", + "util": "^0.12.4", + "typescript": "^4.4.0-dev.20210713", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", - "vscode-debugprotocol": "1.47.0", + "vscode-debugprotocol": "1.48.0-pre.0", "vscode-nls-dev": "^3.3.1", "vscode-telemetry-extractor": "^1.8.0", - "webpack": "^4.43.0", - "webpack-cli": "^3.3.12", - "webpack-stream": "^5.2.1", + "webpack": "^5.42.0", + "webpack-cli": "^4.7.2", + "webpack-stream": "^6.1.2", "xml2js": "^0.4.17", "yaserver": "^0.2.0" }, @@ -223,4 +225,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} \ No newline at end of file +} diff --git a/src/main.js b/src/main.js index f91f7b731d1bd2deeb38488aa1bd95ab48d7d5ba..9cf737e0649cb16bc1f0153aa88a85d3ef6f949f 100644 --- a/src/main.js +++ b/src/main.js @@ -48,7 +48,18 @@ const argvConfig = configureCommandlineSwitchesSync(args); // Configure crash reporter perf.mark('code/willStartCrashReporter'); -configureCrashReporter(); +// If a crash-reporter-directory is specified we store the crash reports +// in the specified directory and don't upload them to the crash server. +// +// Appcenter crash reporting is enabled if +// * enable-crash-reporter runtime argument is set to 'true' +// * --disable-crash-reporter command line parameter is not set +// +// Disable crash reporting in all other cases. +if (args['crash-reporter-directory'] || + (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) { + configureCrashReporter(); +} perf.mark('code/didStartCrashReporter'); // Set logs path before app 'ready' event if running portable @@ -321,8 +332,6 @@ function getArgvConfigPath() { function configureCrashReporter() { - // If a crash-reporter-directory is specified we store the crash reports - // in the specified directory and don't upload them to the crash server. let crashReporterDirectory = args['crash-reporter-directory']; let submitURL = ''; if (crashReporterDirectory) { @@ -351,11 +360,7 @@ function configureCrashReporter() { // Otherwise we configure the crash reporter from product.json else { const appCenter = product.appCenter; - // Disable Appcenter crash reporting if - // * --crash-reporter-directory is specified - // * enable-crash-reporter runtime argument is set to 'false' - // * --disable-crash-reporter command line parameter is set - if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) { + if (appCenter) { const isWindows = (process.platform === 'win32'); const isLinux = (process.platform === 'linux'); const isDarwin = (process.platform === 'darwin'); @@ -410,13 +415,23 @@ function configureCrashReporter() { // Start crash reporter for all processes const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; - crashReporter.start({ - companyName: companyName, - productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, - submitURL, - uploadToServer: !crashReporterDirectory, - compress: true - }); + if (process.env['VSCODE_DEV']) { + crashReporter.start({ + companyName: companyName, + productName: `${productName} Dev`, + submitURL, + uploadToServer: false, + compress: true + }); + } else { + crashReporter.start({ + companyName: companyName, + productName: productName, + submitURL, + uploadToServer: !crashReporterDirectory, + compress: true + }); + } } /** diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index 5659f50db6256294b1f20fe77c2cff964a41d695..a309a50adaaa2475364a1164aff44bd344befa30 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -39,7 +39,7 @@ "ES2020.Intl", "ES2021.Promise", "ES2021.String", - "ES2021.Weakref", + "ES2021.WeakRef", "DOM", "DOM.Iterable", "WebWorker.ImportScripts" diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index aeb837a6da7948a3b3ea8d93c8d505e76fc928ec..bfcaee41f98ade3a33bb5e05e3ebaa17582fa12a 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -35,7 +35,7 @@ padding-left: 0px; padding-right: 0px; line-height: 16px; - margin-left: -4px; + margin-left: -3px; } .monaco-dropdown-with-primary > .dropdown-action-container > .monaco-dropdown > .dropdown-label > .action-label { diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index c7a2907e8992954201716217a7b829a9f6c06e51..c402061670242ed4d10da169629dcd8b15f01476 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -311,6 +311,27 @@ export class MutableDisposable implements IDisposable { } } +export class RefCountedDisposable { + + private _counter: number = 1; + + constructor( + private readonly _disposable: IDisposable, + ) { } + + acquire() { + this._counter++; + return this; + } + + release() { + if (--this._counter === 0) { + this._disposable.dispose(); + } + return this; + } +} + export interface IReference extends IDisposable { readonly object: T; } diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 3e431196b998f15fe71b8a3def1595362d98ce9d..5525a20d60511bb88839478dbe768bf491f615eb 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -287,6 +287,6 @@ export function NotImplementedProxy(name: string): { new(): T } { }; } -export function assertNever(value: never) { - throw new Error('Unreachable'); +export function assertNever(value: never, message = 'Unreachable') { + throw new Error(message); } diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index cd852630bb6754622eb4c45c2fb95cd3fd132646..9d58c50b093d08f76e1907e936eb62ae8b401419 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -342,18 +342,13 @@ // Use `contextBridge` APIs to expose globals to VSCode // only if context isolation is enabled, otherwise just // add to the DOM global. - let useContextBridge = process.argv.includes('--context-isolation'); - if (useContextBridge) { + if (process.contextIsolated) { try { contextBridge.exposeInMainWorld('vscode', globals); } catch (error) { console.error(error); - - useContextBridge = false; } - } - - if (!useContextBridge) { + } else { // @ts-ignore window.vscode = globals; } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c1fb8f8b40770562fcb609cbbfa8e80640101240..135f444bdbf1711e1bf9044158138dc6887fd204 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -124,11 +124,16 @@ export class CodeApplication extends Disposable { // !!! DO NOT CHANGE without consulting the documentation !!! // - const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`); + const isUrlFromWebview = (requestingUrl: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeWebview}://`); + + const allowedPermissionsInWebview = new Set([ + 'clipboard-read', + 'clipboard-sanitized-write', + ]); session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => { if (isUrlFromWebview(details.requestingUrl)) { - return callback(permission === 'clipboard-read'); + return callback(allowedPermissionsInWebview.has(permission)); } return callback(false); @@ -136,7 +141,7 @@ export class CodeApplication extends Disposable { session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => { if (isUrlFromWebview(details.requestingUrl)) { - return permission === 'clipboard-read'; + return allowedPermissionsInWebview.has(permission); } return false; diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 7e7ddddee84751d2ad2981c51e89b47173114ac5..48e8b5a73cf43d24690bc38d3f3a12e7637f00b3 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AbstractCodeEditorService } from 'vs/editor/browser/services/abstractCodeEditorService'; import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon'; -import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IColorTheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; export class RefCountedStyleSheet { @@ -250,6 +250,8 @@ export class DecorationTypeOptionsProvider implements IModelDecorationOptionsPro public isWholeLine: boolean; public overviewRuler: IModelDecorationOverviewRulerOptions | undefined; public stickiness: TrackedRangeStickiness | undefined; + public beforeInjectedText: InjectedTextOptions | undefined; + public afterInjectedText: InjectedTextOptions | undefined; constructor(description: string, themeService: IThemeService, styleSheet: GlobalStyleSheet | RefCountedStyleSheet, providerArgs: ProviderArguments) { this.description = description; @@ -283,6 +285,25 @@ export class DecorationTypeOptionsProvider implements IModelDecorationOptionsPro } this.beforeContentClassName = createCSSRules(ModelDecorationCSSRuleType.BeforeContentClassName); this.afterContentClassName = createCSSRules(ModelDecorationCSSRuleType.AfterContentClassName); + + if (providerArgs.options.beforeInjectedText && providerArgs.options.beforeInjectedText.contentText) { + const beforeInlineData = createInlineCSSRules(ModelDecorationCSSRuleType.BeforeInjectedTextClassName); + this.beforeInjectedText = { + content: providerArgs.options.beforeInjectedText.contentText, + inlineClassName: beforeInlineData?.className, + inlineClassNameAffectsLetterSpacing: beforeInlineData?.hasLetterSpacing || providerArgs.options.beforeInjectedText.affectsLetterSpacing + }; + } + + if (providerArgs.options.afterInjectedText && providerArgs.options.afterInjectedText.contentText) { + const afterInlineData = createInlineCSSRules(ModelDecorationCSSRuleType.AfterInjectedTextClassName); + this.afterInjectedText = { + content: providerArgs.options.afterInjectedText.contentText, + inlineClassName: afterInlineData?.className, + inlineClassNameAffectsLetterSpacing: afterInlineData?.hasLetterSpacing || providerArgs.options.afterInjectedText.affectsLetterSpacing + }; + } + this.glyphMarginClassName = createCSSRules(ModelDecorationCSSRuleType.GlyphMarginClassName); const options = providerArgs.options; @@ -307,6 +328,7 @@ export class DecorationTypeOptionsProvider implements IModelDecorationOptionsPro if (!writable) { return this; } + return { description: this.description, inlineClassName: this.inlineClassName, @@ -316,7 +338,8 @@ export class DecorationTypeOptionsProvider implements IModelDecorationOptionsPro glyphMarginClassName: this.glyphMarginClassName, isWholeLine: this.isWholeLine, overviewRuler: this.overviewRuler, - stickiness: this.stickiness + stickiness: this.stickiness, + before: this.beforeInjectedText }; } @@ -461,6 +484,16 @@ class DecorationCSSRules { lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.after); darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.after); break; + case ModelDecorationCSSRuleType.BeforeInjectedTextClassName: + unthemedCSS = this.getCSSTextForModelDecorationContentClassName(options.beforeInjectedText); + lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.beforeInjectedText); + darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.beforeInjectedText); + break; + case ModelDecorationCSSRuleType.AfterInjectedTextClassName: + unthemedCSS = this.getCSSTextForModelDecorationContentClassName(options.afterInjectedText); + lightCSS = this.getCSSTextForModelDecorationContentClassName(options.light && options.light.afterInjectedText); + darkCSS = this.getCSSTextForModelDecorationContentClassName(options.dark && options.dark.afterInjectedText); + break; default: throw new Error('Unknown rule type: ' + this._ruleType); } @@ -600,7 +633,9 @@ const enum ModelDecorationCSSRuleType { InlineClassName = 1, GlyphMarginClassName = 2, BeforeContentClassName = 3, - AfterContentClassName = 4 + AfterContentClassName = 4, + BeforeInjectedTextClassName = 5, + AfterInjectedTextClassName = 6, } class CSSNameHelper { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index fd38dafbd887c9cd726113694acc1fe42ed0612f..55da3496fac21a42e12124baf50e551c143b57be 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -620,6 +620,9 @@ export interface IThemeDecorationRenderOptions { before?: IContentDecorationRenderOptions; after?: IContentDecorationRenderOptions; + + beforeInjectedText?: IContentDecorationRenderOptions & { affectsLetterSpacing?: boolean }; + afterInjectedText?: IContentDecorationRenderOptions & { affectsLetterSpacing?: boolean }; } /** diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 376e4f280fbce783c4053287586b682460579b52..2617e7488aecd1a06b0aef69d3116b47e3bb65ba 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -118,12 +118,12 @@ export class CoordinatesConverter implements ICoordinatesConverter { // Model -> View conversion and related methods - public convertModelPositionToViewPosition(modelPosition: Position): Position { - return this._lines.convertModelPositionToViewPosition(modelPosition.lineNumber, modelPosition.column); + public convertModelPositionToViewPosition(modelPosition: Position, affinity?: PositionAffinity): Position { + return this._lines.convertModelPositionToViewPosition(modelPosition.lineNumber, modelPosition.column, affinity); } - public convertModelRangeToViewRange(modelRange: Range): Range { - return this._lines.convertModelRangeToViewRange(modelRange); + public convertModelRangeToViewRange(modelRange: Range, affinity?: PositionAffinity): Range { + return this._lines.convertModelRangeToViewRange(modelRange, affinity); } public modelPositionIsVisible(modelPosition: Position): boolean { @@ -886,9 +886,12 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return r; } - public convertModelRangeToViewRange(modelRange: Range): Range { + /** + * @param affinity The affinity in case of an empty range. Has no effect for non-empty ranges. + */ + public convertModelRangeToViewRange(modelRange: Range, affinity: PositionAffinity = PositionAffinity.Left): Range { if (modelRange.isEmpty()) { - const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Left); + const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, affinity); return Range.fromPositions(start); } else { const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Right); diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index c95364e88daacc829895f61053856208b7e9aa8a..bf52f0f45908e9097706190e6ceeb7b5b09f2776 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -82,8 +82,11 @@ export interface ICoordinatesConverter { validateViewRange(viewRange: Range, expectedModelRange: Range): Range; // Model -> View conversion and related methods - convertModelPositionToViewPosition(modelPosition: Position): Position; - convertModelRangeToViewRange(modelRange: Range): Range; + convertModelPositionToViewPosition(modelPosition: Position, affinity?: PositionAffinity): Position; + /** + * @param affinity Only has an effect if the range is empty. + */ + convertModelRangeToViewRange(modelRange: Range, affinity?: PositionAffinity): Range; modelPositionIsVisible(modelPosition: Position): boolean; getModelLineViewLineCount(modelLineNumber: number): number; } diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index c30da7ecdf431aed5ed644c9f7b786c7528e83b2..dd8941d88b559959edb02b74fbeb7bba814170cd 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -7,7 +7,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IModelDecoration, ITextModel } from 'vs/editor/common/model'; +import { IModelDecoration, ITextModel, PositionAffinity } from 'vs/editor/common/model'; import { IViewModelLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ICoordinatesConverter, InlineDecoration, InlineDecorationType, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; @@ -81,11 +81,13 @@ export class ViewModelDecorations implements IDisposable { const options = modelDecoration.options; let viewRange: Range; if (options.isWholeLine) { - const start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1)); - const end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber))); + const start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1), PositionAffinity.Left); + const end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber)), PositionAffinity.Right); viewRange = new Range(start.lineNumber, start.column, end.lineNumber, end.column); } else { - viewRange = this._coordinatesConverter.convertModelRangeToViewRange(modelRange); + // For backwards compatibility reasons, we want injected text before any decoration. + // Thus, move decorations to the right. + viewRange = this._coordinatesConverter.convertModelRangeToViewRange(modelRange, PositionAffinity.Right); } r = new ViewModelDecoration(viewRange, options); this._decorationsCache[id] = r; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index e82dc8afd627d98e78aa11219e737a955fbde909..6e6350d6ffc808c34bdb11d8f52eef8216e6be57 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -26,6 +26,7 @@ import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { Position } from 'vs/editor/common/core/position'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const MAX_DECORATORS = 500; @@ -56,7 +57,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); - private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 250, 2500); + private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 2500); private _decorationsTypeIds: string[] = []; private _decorationIds: string[] = []; @@ -65,6 +66,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _editor: ICodeEditor, @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { this._disposables.add(InlayHintsProviderRegistry.onDidChange(() => this._update())); this._disposables.add(_themeService.onDidColorThemeChange(() => this._update())); @@ -145,6 +147,9 @@ export class InlayHintsController implements IEditorContribution { const fontFamilyVar = '--inlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); + const key = this._configurationService.getValue('editor.useInjectedText'); + const shouldUseInjectedText = key === undefined ? true : !!key; + for (const { list: hints } of hintsData) { for (let j = 0; j < hints.length && newDecorationsData.length < MAX_DECORATORS; j++) { @@ -163,7 +168,8 @@ export class InlayHintsController implements IEditorContribution { borderRadius: `${(fontSize / 4) | 0}px`, }; const key = 'inlayHints-' + hash(before).toString(16); - this._codeEditorService.registerDecorationType('inlay-hints-controller', key, { before }, undefined, this._editor); + this._codeEditorService.registerDecorationType('inlay-hints-controller', key, + shouldUseInjectedText ? { beforeInjectedText: { ...before, affectsLetterSpacing: true } } : { before }, undefined, this._editor); // decoration types are ref-counted which means we only need to // call register und remove equally often diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 04bf8790174e39a58ebf45b92766255fb7291c3a..cf562110373a000ff1134a533725bae8e0677673 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -79,7 +79,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { })); } - private createParamaterHintDOMNodes() { + private createParameterHintDOMNodes() { const element = $('.editor-widget.parameter-hints-widget'); const wrapper = dom.append(element, $('.phwrapper')); wrapper.tabIndex = -1; @@ -150,7 +150,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { } if (!this.domNodes) { - this.createParamaterHintDOMNodes(); + this.createParameterHintDOMNodes(); } this.keyVisible.set(true); @@ -352,7 +352,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { getDomNode(): HTMLElement { if (!this.domNodes) { - this.createParamaterHintDOMNodes(); + this.createParameterHintDOMNodes(); } return this.domNodes!.element; } diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index 0113f863690d035541eeb67fbd2713fb56ad91d6..071a2a290d9ceca73e86c787cc9cf4b1bceb8276 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -255,33 +255,37 @@ export class TimeBasedVariableResolver implements VariableResolver { private static readonly monthNames = [nls.localize('January', "January"), nls.localize('February', "February"), nls.localize('March', "March"), nls.localize('April', "April"), nls.localize('May', "May"), nls.localize('June', "June"), nls.localize('July', "July"), nls.localize('August', "August"), nls.localize('September', "September"), nls.localize('October', "October"), nls.localize('November', "November"), nls.localize('December', "December")]; private static readonly monthNamesShort = [nls.localize('JanuaryShort', "Jan"), nls.localize('FebruaryShort', "Feb"), nls.localize('MarchShort', "Mar"), nls.localize('AprilShort', "Apr"), nls.localize('MayShort', "May"), nls.localize('JuneShort', "Jun"), nls.localize('JulyShort', "Jul"), nls.localize('AugustShort', "Aug"), nls.localize('SeptemberShort', "Sep"), nls.localize('OctoberShort', "Oct"), nls.localize('NovemberShort', "Nov"), nls.localize('DecemberShort', "Dec")]; + constructor(private readonly _date: Date = new Date()) { + // + } + resolve(variable: Variable): string | undefined { const { name } = variable; if (name === 'CURRENT_YEAR') { - return String(new Date().getFullYear()); + return String(this._date.getFullYear()); } else if (name === 'CURRENT_YEAR_SHORT') { - return String(new Date().getFullYear()).slice(-2); + return String(this._date.getFullYear()).slice(-2); } else if (name === 'CURRENT_MONTH') { - return String(new Date().getMonth().valueOf() + 1).padStart(2, '0'); + return String(this._date.getMonth().valueOf() + 1).padStart(2, '0'); } else if (name === 'CURRENT_DATE') { - return String(new Date().getDate().valueOf()).padStart(2, '0'); + return String(this._date.getDate().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_HOUR') { - return String(new Date().getHours().valueOf()).padStart(2, '0'); + return String(this._date.getHours().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_MINUTE') { - return String(new Date().getMinutes().valueOf()).padStart(2, '0'); + return String(this._date.getMinutes().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_SECOND') { - return String(new Date().getSeconds().valueOf()).padStart(2, '0'); + return String(this._date.getSeconds().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_DAY_NAME') { - return TimeBasedVariableResolver.dayNames[new Date().getDay()]; + return TimeBasedVariableResolver.dayNames[this._date.getDay()]; } else if (name === 'CURRENT_DAY_NAME_SHORT') { - return TimeBasedVariableResolver.dayNamesShort[new Date().getDay()]; + return TimeBasedVariableResolver.dayNamesShort[this._date.getDay()]; } else if (name === 'CURRENT_MONTH_NAME') { - return TimeBasedVariableResolver.monthNames[new Date().getMonth()]; + return TimeBasedVariableResolver.monthNames[this._date.getMonth()]; } else if (name === 'CURRENT_MONTH_NAME_SHORT') { - return TimeBasedVariableResolver.monthNamesShort[new Date().getMonth()]; + return TimeBasedVariableResolver.monthNamesShort[this._date.getMonth()]; } else if (name === 'CURRENT_SECONDS_UNIX') { - return String(Math.floor(Date.now() / 1000)); + return String(Math.floor(this._date.getTime() / 1000)); } return undefined; diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index bd687c661846fe54ae90b884ea03846218a2a302..15530055123da037b9be1ba291f90cbc6557c3aa 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -17,6 +17,7 @@ import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { sep } from 'vs/base/common/path'; import { toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; +import * as sinon from 'sinon'; suite('Snippet Variables Resolver', function () { @@ -291,6 +292,36 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve3(resolver, 'CURRENT_SECONDS_UNIX'); }); + test('Time-based snippet variables resolve to the same values even as time progresses', async function () { + const snippetText = ` + $CURRENT_YEAR + $CURRENT_YEAR_SHORT + $CURRENT_MONTH + $CURRENT_DATE + $CURRENT_HOUR + $CURRENT_MINUTE + $CURRENT_SECOND + $CURRENT_DAY_NAME + $CURRENT_DAY_NAME_SHORT + $CURRENT_MONTH_NAME + $CURRENT_MONTH_NAME_SHORT + $CURRENT_SECONDS_UNIX + `; + + const clock = sinon.useFakeTimers(); + try { + const resolver = new TimeBasedVariableResolver; + + const firstResolve = new SnippetParser().parse(snippetText).resolveVariables(resolver); + clock.tick((365 * 24 * 3600 * 1000) + (24 * 3600 * 1000) + (3661 * 1000)); // 1 year + 1 day + 1 hour + 1 minute + 1 second + const secondResolve = new SnippetParser().parse(snippetText).resolveVariables(resolver); + + assert.strictEqual(firstResolve.toString(), secondResolve.toString(), `Time-based snippet variables resolved differently`); + } finally { + clock.restore(); + } + }); + test('creating snippet - format-condition doesn\'t work #53617', function () { const snippet = new SnippetParser().parse('${TM_LINE_NUMBER/(10)/${1:?It is:It is not}/} line 10', true); diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts index 21d34ce6393f5411c8c597d81663f90b57379639..f6e5271ea6667c7717d04120069b003c7200ec75 100644 --- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts @@ -116,9 +116,6 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec4', InlineDecorationType.Before), new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec5', InlineDecorationType.Before), - new InlineDecoration(new Range(1, 14, 1, 14), 'i-dec6', InlineDecorationType.Regular), - new InlineDecoration(new Range(1, 14, 1, 14), 'b-dec6', InlineDecorationType.Before), - new InlineDecoration(new Range(1, 14, 1, 14), 'a-dec6', InlineDecorationType.After), ]); const inlineDecorations2 = viewModel.getViewLineRenderingData( @@ -132,6 +129,9 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(2, 2, 2, 2), 'a-dec3', InlineDecorationType.After), new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular), new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'i-dec6', InlineDecorationType.Regular), + new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec6', InlineDecorationType.Before), + new InlineDecoration(new Range(2, 1, 2, 1), 'a-dec6', InlineDecorationType.After), new InlineDecoration(new Range(2, 1, 2, 3), 'i-dec7', InlineDecorationType.Regular), new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec7', InlineDecorationType.Before), new InlineDecoration(new Range(2, 3, 2, 3), 'a-dec7', InlineDecorationType.After), @@ -164,9 +164,6 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(2, 3, 3, 13), 'i-dec11', InlineDecorationType.Regular), new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec11', InlineDecorationType.After), new InlineDecoration(new Range(2, 3, 5, 8), 'i-dec12', InlineDecorationType.Regular), - new InlineDecoration(new Range(3, 13, 3, 13), 'i-dec13', InlineDecorationType.Regular), - new InlineDecoration(new Range(3, 13, 3, 13), 'b-dec13', InlineDecorationType.Before), - new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec13', InlineDecorationType.After), ]); }); }); diff --git a/src/vs/platform/environment/node/userDataPath.d.ts b/src/vs/platform/environment/node/userDataPath.d.ts index a3cd8b73b1b7a3b27377d6f2c7b9d7357e9db7d3..4c9239fc9535ab93c162eaab5c5176edbee98d02 100644 --- a/src/vs/platform/environment/node/userDataPath.d.ts +++ b/src/vs/platform/environment/node/userDataPath.d.ts @@ -8,7 +8,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; /** * Returns the user data path to use with some rules: * - respect portable mode - * - respect --user-data-dir CLI argument * - respect VSCODE_APPDATA environment variable + * - respect --user-data-dir CLI argument */ export function getUserDataPath(args: NativeParsedArgs): string; diff --git a/src/vs/platform/environment/node/userDataPath.js b/src/vs/platform/environment/node/userDataPath.js index b1c1fb26b5dbcf64918343797466621c485d394f..1f7196c33e162c0f9ca9f5e791118406c477c167 100644 --- a/src/vs/platform/environment/node/userDataPath.js +++ b/src/vs/platform/environment/node/userDataPath.js @@ -54,38 +54,42 @@ return path.join(portablePath, 'user-data'); } - // 2. Support explicit --user-data-dir + // 2. Support global VSCODE_APPDATA environment variable + let appDataPath = process.env['VSCODE_APPDATA']; + if (appDataPath) { + return path.join(appDataPath, productName); + } + + // With Electron>=13 --user-data-dir switch will be propagated to + // all processes https://github.com/electron/electron/blob/1897b14af36a02e9aa7e4d814159303441548251/shell/browser/electron_browser_client.cc#L546-L553 + // Check VSCODE_PORTABLE and VSCODE_APPDATA before this case to get correct values. + // 3. Support explicit --user-data-dir const cliPath = cliArgs['user-data-dir']; if (cliPath) { return cliPath; } - // 3. Support global VSCODE_APPDATA environment variable - let appDataPath = process.env['VSCODE_APPDATA']; - // 4. Otherwise check per platform - if (!appDataPath) { - switch (process.platform) { - case 'win32': - appDataPath = process.env['APPDATA']; - if (!appDataPath) { - const userProfile = process.env['USERPROFILE']; - if (typeof userProfile !== 'string') { - throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable'); - } - - appDataPath = path.join(userProfile, 'AppData', 'Roaming'); + switch (process.platform) { + case 'win32': + appDataPath = process.env['APPDATA']; + if (!appDataPath) { + const userProfile = process.env['USERPROFILE']; + if (typeof userProfile !== 'string') { + throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable'); } - break; - case 'darwin': - appDataPath = path.join(os.homedir(), 'Library', 'Application Support'); - break; - case 'linux': - appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); - break; - default: - throw new Error('Platform not supported'); - } + + appDataPath = path.join(userProfile, 'AppData', 'Roaming'); + } + break; + case 'darwin': + appDataPath = path.join(os.homedir(), 'Library', 'Application Support'); + break; + case 'linux': + appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); + break; + default: + throw new Error('Platform not supported'); } return path.join(appDataPath, productName); diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 22fd82a67f67a17dac7bf966e1fc7df2bb7666f4..e55de7ea4c21480d35ceefdf1af0514ef293391e 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -316,7 +316,7 @@ export class IssueMainService implements ICommonIssueService { backgroundColor: options.backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`, '--context-isolation' /* TODO@bpasero: Use process.contextIsolateed when 13-x-y is adopted (https://github.com/electron/electron/pull/28030) */], + additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, spellcheck: false, diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 15439db956b9a70f82717e449077a9560809dfa9..6fea3dafc1407bfc213e6cad55c1dc6e6f663558 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -67,6 +67,7 @@ export const enum TerminalSettingId { RightClickBehavior = 'terminal.integrated.rightClickBehavior', Cwd = 'terminal.integrated.cwd', ConfirmOnExit = 'terminal.integrated.confirmOnExit', + ConfirmOnKill = 'terminal.integrated.confirmOnKill', EnableBell = 'terminal.integrated.enableBell', CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell', AllowChords = 'terminal.integrated.allowChords', @@ -185,6 +186,8 @@ export interface IPtyService { readonly onProcessResolvedShellLaunchConfig: Event<{ id: number, event: IShellLaunchConfig }>; readonly onProcessReplay: Event<{ id: number, event: IPtyHostProcessReplayEvent }>; readonly onProcessOrphanQuestion: Event<{ id: number }>; + readonly onDidRequestDetach: Event<{ requestId: number, workspaceId: string, instanceId: number }>; + readonly onProcessDidChangeHasChildProcesses: Event<{ id: number, event: boolean }>; restartPtyHost?(): Promise; shutdownAll?(): Promise; @@ -230,6 +233,8 @@ export interface IPtyService { setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise; getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise; reduceConnectionGraceTime(): Promise; + requestDetachInstance(workspaceId: string, instanceId: number): Promise; + acceptDetachedInstance(requestId: number, process: number): Promise; } export interface IRequestResolveVariablesEvent { @@ -457,9 +462,10 @@ export interface ITerminalChildProcess { onProcessExit: Event; onProcessReady: Event; onProcessTitleChanged: Event; + onProcessShellTypeChanged: Event; onProcessOverrideDimensions?: Event; onProcessResolvedShellLaunchConfig?: Event; - onProcessShellTypeChanged: Event; + onDidChangeHasChildProcesses?: Event; /** * Starts the process. diff --git a/src/vs/platform/terminal/node/childProcessMonitor.ts b/src/vs/platform/terminal/node/childProcessMonitor.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b9fdcee93ceaee77e5424b9ebb2d182310213b0 --- /dev/null +++ b/src/vs/platform/terminal/node/childProcessMonitor.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { debounce, throttle } from 'vs/base/common/decorators'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter } from 'vs/base/common/event'; +import { listProcesses } from 'vs/base/node/ps'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ProcessItem } from 'vs/base/common/processes'; +import { parse } from 'path'; + +const enum Constants { + /** + * The amount of time to throttle checks when the process receives output. + */ + InactiveThrottleDuration = 5000, + /** + * The amount of time to debounce check when the process receives input. + */ + ActiveDebounceDuration = 1000, +} + +const ignoreProcessNames = [ + // Popular prompt programs, these should not count as child processes + 'starship', + 'oh-my-posh' +]; + +/** + * Monitors a process for child processes, checking at differing times depending on input and output + * calls into the monitor. + */ +export class ChildProcessMonitor extends Disposable { + private _isDisposed: boolean = false; + + private _hasChildProcesses: boolean = false; + private set hasChildProcesses(value: boolean) { + if (this._hasChildProcesses !== value) { + this._hasChildProcesses = value; + this._logService.debug('ChildProcessMonitor: Has child processes changed', value); + this._onDidChangeHasChildProcesses.fire(value); + } + } + /** + * Whether the process has child processes. + */ + get hasChildProcesses(): boolean { return this._hasChildProcesses; } + + private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); + /** + * An event that fires when whether the process has child processes changes. + */ + readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; + + constructor( + private readonly _pid: number, + @ILogService private readonly _logService: ILogService + ) { + super(); + } + + override dispose() { + this._isDisposed = true; + super.dispose(); + } + + /** + * Input was triggered on the process. + */ + handleInput() { + this._refreshActive(); + } + + /** + * Output was triggered on the process. + */ + handleOutput() { + this._refreshInactive(); + } + + @debounce(Constants.ActiveDebounceDuration) + private async _refreshActive(): Promise { + if (this._isDisposed) { + return; + } + try { + const processItem = await listProcesses(this._pid); + this.hasChildProcesses = this._processContainsChildren(processItem); + } catch (e) { + this._logService.debug('ChildProcessMonitor: Fetching process tree failed', e); + } + } + + @throttle(Constants.InactiveThrottleDuration) + private _refreshInactive(): void { + this._refreshActive(); + } + + private _processContainsChildren(processItem: ProcessItem): boolean { + // No child processes + if (!processItem.children) { + return false; + } + + // A single child process, handle special cases + if (processItem.children.length === 1) { + const item = processItem.children[0]; + let cmd: string; + if (item.cmd.startsWith(`"`)) { + cmd = item.cmd.substring(1, item.cmd.indexOf(`"`, 1)); + } else { + const spaceIndex = item.cmd.indexOf(` `); + if (spaceIndex === -1) { + cmd = item.cmd; + } else { + cmd = item.cmd.substring(0, spaceIndex); + } + } + return ignoreProcessNames.indexOf(parse(cmd).name) === -1; + } + + // Fallback, count child processes + return processItem.children.length > 0; + } +} diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index eee9e39549691204e3a67b0cf64035ea68fe21fe..4bf9f0e2ad48cd06de69a4092348e3dd44963492 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -77,6 +77,10 @@ export class PtyHostService extends Disposable implements IPtyService { readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; private readonly _onProcessOrphanQuestion = this._register(new Emitter<{ id: number }>()); readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event; + private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number, workspaceId: string, instanceId: number }>()); + readonly onDidRequestDetach = this._onDidRequestDetach.event; + private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter<{ id: number, event: boolean }>()); + readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event; constructor( private readonly _reconnectConstants: IReconnectConstants, @@ -152,8 +156,10 @@ export class PtyHostService extends Disposable implements IPtyService { this._register(proxy.onProcessShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e))); this._register(proxy.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e))); this._register(proxy.onProcessResolvedShellLaunchConfig(e => this._onProcessResolvedShellLaunchConfig.fire(e))); + this._register(proxy.onProcessDidChangeHasChildProcesses(e => this._onProcessDidChangeHasChildProcesses.fire(e))); this._register(proxy.onProcessReplay(e => this._onProcessReplay.fire(e))); this._register(proxy.onProcessOrphanQuestion(e => this._onProcessOrphanQuestion.fire(e))); + this._register(proxy.onDidRequestDetach(e => this._onDidRequestDetach.fire(e))); return [client, proxy]; } @@ -239,6 +245,14 @@ export class PtyHostService extends Disposable implements IPtyService { return await this._proxy.getTerminalLayoutInfo(args); } + async requestDetachInstance(workspaceId: string, instanceId: number): Promise { + return this._proxy.requestDetachInstance(workspaceId, instanceId); + } + + async acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise { + return this._proxy.acceptDetachedInstance(requestId, persistentProcessId); + } + async restartPtyHost(): Promise { /* __GDPR__ "ptyHost/restart" : {} diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index e201c1f4560a890713dee992510a3b4343167863..4d088b43c2197f1202f0b4ac126e45e158488f5e 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -20,6 +20,7 @@ import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnviro import { URI } from 'vs/base/common/uri'; type WorkspaceId = string; +let lastResolvedInstanceRequestId = 0; export class PtyService extends Disposable implements IPtyService { declare readonly _serviceBrand: undefined; @@ -48,6 +49,10 @@ export class PtyService extends Disposable implements IPtyService { readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; private readonly _onProcessOrphanQuestion = this._register(new Emitter<{ id: number }>()); readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event; + private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number, workspaceId: string, instanceId: number }>()); + readonly onDidRequestDetach = this._onDidRequestDetach.event; + private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter<{ id: number, event: boolean }>()); + readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event; constructor( private _lastPtyId: number, @@ -64,6 +69,29 @@ export class PtyService extends Disposable implements IPtyService { })); } + private _pendingDetachInstanceRequests: Map void> = new Map(); + async requestDetachInstance(workspaceId: string, instanceId: number): Promise { + return new Promise(resolve => { + const requestId = ++lastResolvedInstanceRequestId; + this._pendingDetachInstanceRequests.set(requestId, resolve); + this._onDidRequestDetach.fire({ requestId, workspaceId, instanceId }); + }); + } + + async acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise { + const request = this._pendingDetachInstanceRequests.get(requestId); + if (request) { + this._pendingDetachInstanceRequests.delete(requestId); + const pty = this._throwIfNoPty(persistentProcessId); + const process = await this._buildProcessDetails(persistentProcessId, pty); + request(process); + return process; + } else { + this._logService.warn(`Accept detached instance was called without receiving a matching request ${requestId} for process with ID: ${persistentProcessId}`); + return undefined; + } + } + async shutdownAll(): Promise { this.dispose(); } @@ -93,6 +121,9 @@ export class PtyService extends Disposable implements IPtyService { if (process.onProcessResolvedShellLaunchConfig) { process.onProcessResolvedShellLaunchConfig(event => this._onProcessResolvedShellLaunchConfig.fire({ id, event })); } + if (process.onDidChangeHasChildProcesses) { + process.onDidChangeHasChildProcesses(event => this._onProcessDidChangeHasChildProcesses.fire({ id, event })); + } const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._reconnectConstants, this._logService, shellLaunchConfig.icon); process.onProcessExit(() => { persistentProcess.dispose(); diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 9a15be8881347fb7dcf7802a53037fa7fca95db4..d778546a735cdf29741cda7ed28201a52609f8f9 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -18,13 +18,7 @@ import { WindowsShellHelper } from 'vs/platform/terminal/node/windowsShellHelper import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { timeout } from 'vs/base/common/async'; import { Promises } from 'vs/base/node/pfs'; - -// Writing large amounts of data can be corrupted for some reason, after looking into this is -// appears to be a race condition around writing to the FD which may be based on how powerful the -// hardware is. The workaround for this is to space out when large amounts of data is being written -// to the terminal. See https://github.com/microsoft/vscode/issues/38137 -const WRITE_MAX_CHUNK_SIZE = 50; -const WRITE_INTERVAL_MS = 5; +import { ChildProcessMonitor } from 'vs/platform/terminal/node/childProcessMonitor'; const enum ShutdownConstants { /** @@ -60,6 +54,18 @@ const enum Constants { * interval. */ KillSpawnSpacingDuration = 50, + + /** + * Writing large amounts of data can be corrupted for some reason, after looking into this is + * appears to be a race condition around writing to the FD which may be based on how powerful + * the hardware is. The workaround for this is to space out when large amounts of data is being + * written to the terminal. See https://github.com/microsoft/vscode/issues/38137 + */ + WriteMaxChunkSize = 50, + /** + * How long to wait between chunk writes. + */ + WriteInterval = 5, } interface IWriteObject { @@ -81,6 +87,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess private _processStartupComplete: Promise | undefined; private _isDisposed: boolean = false; private _windowsShellHelper: WindowsShellHelper | undefined; + private _childProcessMonitor: ChildProcessMonitor | undefined; private _titleInterval: NodeJS.Timer | null = null; private _writeQueue: IWriteObject[] = []; private _writeTimeout: NodeJS.Timeout | undefined; @@ -96,15 +103,20 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess get shellType(): TerminalShellType { return this._windowsShellHelper ? this._windowsShellHelper.shellType : undefined; } private readonly _onProcessData = this._register(new Emitter()); - get onProcessData(): Event { return this._onProcessData.event; } + readonly onProcessData = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter()); - get onProcessExit(): Event { return this._onProcessExit.event; } + readonly onProcessExit = this._onProcessExit.event; private readonly _onProcessReady = this._register(new Emitter()); - get onProcessReady(): Event { return this._onProcessReady.event; } + readonly onProcessReady = this._onProcessReady.event; private readonly _onProcessTitleChanged = this._register(new Emitter()); - get onProcessTitleChanged(): Event { return this._onProcessTitleChanged.event; } + readonly onProcessTitleChanged = this._onProcessTitleChanged.event; private readonly _onProcessShellTypeChanged = this._register(new Emitter()); readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; + private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); + readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; + + onProcessOverrideDimensions?: Event | undefined; + onProcessResolvedShellLaunchConfig?: Event | undefined; constructor( private readonly _shellLaunchConfig: IShellLaunchConfig, @@ -161,8 +173,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess }); } } - onProcessOverrideDimensions?: Event | undefined; - onProcessResolvedShellLaunchConfig?: Event | undefined; async start(): Promise { const results = await Promise.all([this._validateCwd(), this._validateExecutable()]); @@ -227,6 +237,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._logService.trace('IPty#spawn', shellLaunchConfig.executable, args, options); const ptyProcess = (await import('node-pty')).spawn(shellLaunchConfig.executable!, args, options); this._ptyProcess = ptyProcess; + this._childProcessMonitor = this._register(new ChildProcessMonitor(ptyProcess.pid, this._logService)); + this._childProcessMonitor.onDidChangeHasChildProcesses(this._onDidChangeHasChildProcesses.fire, this._onDidChangeHasChildProcesses); this._processStartupComplete = new Promise(c => { this.onProcessReady(() => c()); }); @@ -245,6 +257,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._queueProcessExit(); } this._windowsShellHelper?.checkShell(); + this._childProcessMonitor?.handleOutput(); }); ptyProcess.onExit(e => { this._exitCode = e.exitCode; @@ -359,10 +372,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (this._isDisposed || !this._ptyProcess) { return; } - for (let i = 0; i <= Math.floor(data.length / WRITE_MAX_CHUNK_SIZE); i++) { + for (let i = 0; i <= Math.floor(data.length / Constants.WriteMaxChunkSize); i++) { const obj = { isBinary: isBinary || false, - data: data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE) + data: data.substr(i * Constants.WriteMaxChunkSize, Constants.WriteMaxChunkSize) }; this._writeQueue.push(obj); } @@ -391,7 +404,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._writeTimeout = setTimeout(() => { this._writeTimeout = undefined; this._startWrite(); - }, WRITE_INTERVAL_MS); + }, Constants.WriteInterval); } private _doWrite(): void { @@ -401,6 +414,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } else { this._ptyProcess!.write(object.data); } + this._childProcessMonitor?.handleInput(); } resize(cols: number, rows: number): void { diff --git a/src/vs/platform/webview/common/webviewManagerService.ts b/src/vs/platform/webview/common/webviewManagerService.ts index f7d811b2ba9508499b1f292444323c0173f0676e..b8c17f157d8dcc9c07650769a205ba9212a98711 100644 --- a/src/vs/platform/webview/common/webviewManagerService.ts +++ b/src/vs/platform/webview/common/webviewManagerService.ts @@ -8,8 +8,6 @@ import { Event } from 'vs/base/common/event'; export const IWebviewManagerService = createDecorator('webviewManagerService'); -export const webviewPartitionId = 'webview'; - export interface WebviewWebContentsId { readonly webContentsId: number; } diff --git a/src/vs/platform/webview/electron-main/webviewMainService.ts b/src/vs/platform/webview/electron-main/webviewMainService.ts index dada2f414a6120fafeab945a8f19ebf31f3433f8..45c92dd7d12053b1df17cbef6be9ddf9ab555418 100644 --- a/src/vs/platform/webview/electron-main/webviewMainService.ts +++ b/src/vs/platform/webview/electron-main/webviewMainService.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { session, WebContents, webContents, WebFrameMain } from 'electron'; +import { WebContents, webContents, WebFrameMain } from 'electron'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { FindInFrameOptions, FoundInFrameResult, IWebviewManagerService, webviewPartitionId, WebviewWebContentsId, WebviewWindowId } from 'vs/platform/webview/common/webviewManagerService'; +import { FindInFrameOptions, FoundInFrameResult, IWebviewManagerService, WebviewWebContentsId, WebviewWindowId } from 'vs/platform/webview/common/webviewManagerService'; import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; @@ -19,24 +18,10 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer public onFoundInFrame = this._onFoundInFrame.event; constructor( - @ITunnelService tunnelService: ITunnelService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, ) { super(); this._register(new WebviewProtocolProvider()); - - const sess = session.fromPartition(webviewPartitionId); - sess.setPermissionRequestHandler((_webContents, permission, callback) => { - if (permission === 'clipboard-read') { - return callback(true); - } - - return callback(false); - }); - - sess.setPermissionCheckHandler((_webContents, permission /* 'media' */) => { - return permission === 'clipboard-read'; - }); } public async setIgnoreMenuShortcuts(id: WebviewWebContentsId | WebviewWindowId, enabled: boolean): Promise { diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index f9b32048800cb96bb96eea677e38944488e36e9c..5ee14ecf8d766b9a151af3eec994b4fe7e1bb759 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { protocol, session } from 'electron'; +import { protocol } from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { webviewPartitionId } from 'vs/platform/webview/common/webviewManagerService'; export class WebviewProtocolProvider extends Disposable { @@ -22,12 +21,9 @@ export class WebviewProtocolProvider extends Disposable { constructor() { super(); - const sess = session.fromPartition(webviewPartitionId); - // Register the protocol loading webview html const webviewHandler = this.handleWebviewRequest.bind(this); protocol.registerFileProtocol(Schemas.vscodeWebview, webviewHandler); - sess.protocol.registerFileProtocol(Schemas.vscodeWebview, webviewHandler); } private async handleWebviewRequest( diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index e0821ea0159f9cc4ba19526eeb2869d61add9b94..98e5e82af633c265229e4d5836fc543cc5e2a220 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -186,9 +186,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { title: this.productService.nameLong, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - additionalArguments: this.environmentMainService.sandbox ? - [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`, '--context-isolation' /* TODO@bpasero: Use process.contextIsolateed when 13-x-y is adopted (https://github.com/electron/electron/pull/28030) */] : - [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], + additionalArguments: [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, spellcheck: false, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f50dab3ece64439aec1aba4f28592b4792580605..7df258b41d3f0ea3bfcfa892f8cbcbf1afca2518 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -933,6 +933,20 @@ declare module 'vscode' { } //#endregion + export class TaskGroup2 { + static Clean: TaskGroup2; + static Build: TaskGroup2; + static Rebuild: TaskGroup2; + static Test: TaskGroup2; + readonly isDefault?: boolean; + readonly id: string; + private constructor(id: string, label: string); + } + + export class Task2 extends Task { + group?: TaskGroup2; + } + //#region Custom editor move https://github.com/microsoft/vscode/issues/86146 // TODO: Also for custom editor @@ -1771,13 +1785,14 @@ declare module 'vscode' { * Creates a new test controller. * * @param id Identifier for the controller, must be globally unique. - */ - export function createTestController(id: string): TestController; + */ + export function createTestController(id: string, label: string): TestController; /** * Requests that tests be run by their controller. - * @param run Run options to use + * @param run Run options to use. * @param token Cancellation token for the test run + * @stability experimental */ export function runTests(run: TestRunRequest, token?: CancellationToken): Thenable; @@ -1787,6 +1802,16 @@ declare module 'vscode' { */ export function createTestObserver(): TestObserver; + /** + * Creates a new managed {@link TestItem} instance. It can be added into + * the {@link TestItem.children} of an existing item, or into the + * {@link TestController.items}. + * @param id Unique identifier for the TestItem. + * @param label Human-readable label of the test item. + * @param uri URI this TestItem is associated with. May be a file or directory. + */ + export function createTestItem(id: string, label: string, uri?: Uri): TestItem; + /** * List of test results stored by the editor, sorted in descending * order by their `completedAt` time. @@ -1844,54 +1869,130 @@ declare module 'vscode' { readonly removed: ReadonlyArray; } + // Todo@api: this is basically the same as the TaskGroup, which is a class that + // allows custom groups to be created. However I don't anticipate having any + // UI for that, so enum for now? + export enum TestRunConfigurationGroup { + Run = 1, + Debug = 2, + Coverage = 3, + } + + /** + * Handler called to start a test run. When invoked, the function should + * {@link TestController.createTestRun} at least once, and all tasks + * associated with the run should be created before the function returns + * or the reutrned promise is resolved. + * + * @param request Request information for the test run + * @param cancellationToken Token that signals the used asked to abort the + * test run. If cancellation is requested on this token, all {@link TestRun} + * instances associated with the request will be + * automatically cancelled as well. + */ + // todo@api We have been there with NotebookCtrl#executeHandler and I believe the recommendation is still not to inline. + // At least with that we can still do it later + export type TestRunHandler = (request: TestRunRequest, token: CancellationToken) => Thenable | void; + + export interface TestRunConfiguration { + /** + * Label shown to the user in the UI. + * + * Note that the label has some significance if the user requests that + * tests be re-run in a certain way. For example, if tests were run + * normally and the user requests to re-run them in debug mode, the editor + * will attempt use a configuration with the same label in the `Debug` + * group. If there is no such configuration, the default will be used. + */ + label: string; + + /** + * Configures where this configuration is grouped in the UI. If there + * are no configurations for a group, it will not be available in the UI. + */ + readonly group: TestRunConfigurationGroup; + + /** + * Controls whether this configuration is the default action that will + * be taken when its group is actions. For example, if the user clicks + * the generic "run all" button, then the default configuration for + * {@link TestRunConfigurationGroup.Run} will be executed. + */ + isDefault: boolean; + + /** + * If this method is present a configuration gear will be present in the + * UI, and this method will be invoked when it's clicked. When called, + * you can take other editor actions, such as showing a quick pick or + * opening a configuration file. + */ + configureHandler?: () => void; + + /** + * Starts a test run. When called, the controller should call + * {@link TestController.createTestRun}. All tasks associated with the + * run should be created before the function returns or the reutrned + * promise is resolved. + * + * @param request Request information for the test run + * @param cancellationToken Token that signals the used asked to abort the + * test run. If cancellation is requested on this token, all {@link TestRun} + * instances associated with the request will be + * automatically cancelled as well. + */ + runHandler: TestRunHandler; + + /** + * Deletes the run configuration. + */ + dispose(): void; + } + /** * Interface to discover and execute tests. */ + // todo@api maybe some words on this being the "entry point" export interface TestController { /** * The ID of the controller, passed in {@link vscode.test.createTestController} */ + // todo@api maybe explain what the id is used for and iff it must be globally unique or only unique within the extension readonly id: string; /** - * Root test item. Tests in the workspace should be added as children of - * the root. The extension controls when to add these, although the + * Human-readable label for the test controller. + */ + label: string; + + /** + * Available test items. Tests in the workspace should be added in this + * collection. The extension controls when to add these, although the * editor may request children using the {@link resolveChildrenHandler}, * and the extension should add tests for a file when * {@link vscode.workspace.onDidOpenTextDocument} fires in order for * decorations for tests within the file to be visible. * * Tests in this collection should be watched and updated by the extension - * as files change. See {@link resolveChildrenHandler} for details around + * as files change. See {@link resolveChildrenHandler} for details around * for the lifecycle of watches. */ - // todo@API a little weird? what is its label, id, busy state etc? Can I dispose this? - // todo@API allow createTestItem-calls without parent and simply treat them as root (similar to createSourceControlResourceGroup) - readonly root: TestItem; + readonly items: TestItemCollection; /** - * Creates a new managed {@link TestItem} instance as a child of this - * one. - * @param id Unique identifier for the TestItem. - * @param label Human-readable label of the test item. - * @param parent Parent of the item. This is required; top-level items - * should be created as children of the {@link root}. - * @param uri URI this TestItem is associated with. May be a file or directory. - * @param data Custom data to be stored in {@link TestItem.data} + * Creates a configuration used for running tests. Extensions must create + * at least one configuration in order for tests to be run. + * @param label Human-readable label for this configuration + * @param group Configures where this configuration is grouped in the UI. + * @param runHandler Function called to start a test run + * @param isDefault Whether this is the default action for the group */ - createTestItem( - id: string, - label: string, - parent: TestItem, - uri?: Uri, - ): TestItem; - + createRunConfiguration(label: string, group: TestRunConfigurationGroup, runHandler: TestRunHandler, isDefault?: boolean): TestRunConfiguration; /** * A function provided by the extension that the editor may call to request * children of a test item, if the {@link TestItem.canExpand} is `true`. * When called, the item should discover children and call - * {@link TestController.createTestItem} as children are discovered. + * {@link vscode.test.createTestItem} as children are discovered. * * The item in the explorer will automatically be marked as "busy" until * the function returns or the returned thenable resolves. @@ -1902,21 +2003,9 @@ declare module 'vscode' { * @param item An unresolved test item for which * children are being requested */ + // todo@API maybe just `resolveHandler` so that we could extends its usage in the future? resolveChildrenHandler?: (item: TestItem) => Thenable | void; - /** - * Starts a test run. When called, the controller should call - * {@link TestController.createTestRun}. All tasks associated with the - * run should be created before the function returns or the reutrned - * promise is resolved. - * - * @param request Request information for the test run - * @param cancellationToken Token that signals the used asked to abort the - * test run. If cancellation is requested on this token, all {@link TestRun} - * instances associated with the request will be - * automatically cancelled as well. - */ - runHandler?: (request: TestRunRequest, token: CancellationToken) => Thenable | void; /** * Creates a {@link TestRun}. This should be called by the * {@link TestRunner} when a request is made to execute tests, and may also @@ -1924,6 +2013,10 @@ declare module 'vscode' { * that are included in the results will be moved into the * {@link TestResultState.Pending} state. * + * All runs created using the same `request` instance will be grouped + * together. This is useful if, for example, a single suite of tests is + * run on multiple platforms. + * * @param request Test run request. Only tests inside the `include` may be * modified, and tests in its `exclude` are ignored. * @param name The human-readable name of the run. This can be used to @@ -1947,11 +2040,12 @@ declare module 'vscode' { */ export class TestRunRequest { /** - * Array of specific tests to run. The controllers should run all of the - * given tests and all children of the given tests, excluding any tests - * that appear in {@link TestRunRequest.exclude}. + * Filter for specific tests to run. If given, the extension should run all + * of the given tests and all children of the given tests, excluding + * any tests that appear in {@link TestRunRequest.exclude}. If this is + * not given, then the extension should simply run all tests. */ - tests: TestItem[]; + include?: TestItem[]; /** * An array of tests the user has marked as excluded in the editor. May be @@ -1961,16 +2055,18 @@ declare module 'vscode' { exclude?: TestItem[]; /** - * Whether tests in this run should be debugged. + * The configuration used for this request. This will always be defined + * for requests issued from the editor UI, though extensions may + * programmatically create requests not associated with any configuration. */ - debug: boolean; + configuration?: TestRunConfiguration; /** - * @param tests Array of specific tests to run. + * @param tests Array of specific tests to run, or undefined to run all tests * @param exclude Tests to exclude from the run - * @param debug Whether tests in this run should be debugged. + * @param configuration The run configuration used for this request. */ - constructor(tests: readonly TestItem[], exclude?: readonly TestItem[], debug?: boolean); + constructor(include?: readonly TestItem[], exclude?: readonly TestItem[], configuration?: TestRunConfiguration); } /** @@ -2027,9 +2123,40 @@ declare module 'vscode' { * Signals that the end of the test run. Any tests whose states have not * been updated will be moved into the {@link TestResultState.Unset} state. */ + // todo@api is the Unset logic smart and only considering those tests that are included? end(): void; } + /** + * Collection of test items, found in {@link TestItem.children} and + * {@link TestController.items}. + */ + export interface TestItemCollection { + /** + * A read-only array of all the test items children. Can be retrieved, or + * set in order to replace children in the collection. + */ + // todo@API unsure if this should readonly and have a separate replaceAll-like function + all: readonly TestItem[]; + + /** + * Adds the test item to the children. If an item with the same ID already + * exists, it'll be replaced. + */ + add(item: TestItem): void; + + /** + * Removes the a single test item from the collection. + */ + //todo@API `delete` as Map, EnvironmentVariableCollection, DiagnosticCollection + remove(itemId: string): void; + + /** + * Efficiently gets a test item by ID, if it exists, in the children. + */ + get(itemId: string): TestItem | undefined; + } + /** * A test item is an item shown in the "test explorer" view. It encompasses * both a suite and a test, since they have almost or identical capabilities. @@ -2040,6 +2167,7 @@ declare module 'vscode' { * test results and tests in the document with those in the workspace * (test explorer). This must not change for the lifetime of the TestItem. */ + // todo@API globally vs extension vs controller unique. I would strongly recommend non-global readonly id: string; /** @@ -2050,13 +2178,14 @@ declare module 'vscode' { /** * A mapping of children by ID to the associated TestItem instances. */ - //todo@API use array over es6-map - readonly children: ReadonlyMap; + readonly children: TestItemCollection; /** - * The parent of this item, given in {@link TestController.createTestItem}. - * This is undefined only for the {@link TestController.root}. + * The parent of this item, given in {@link vscode.test.createTestItem}. + * This is undefined top-level items in the `TestController`, and for + * items that aren't yet assigned to a parent. */ + // todo@api obsolete? doc is outdated at least readonly parent?: TestItem; /** @@ -2099,18 +2228,6 @@ declare module 'vscode' { */ error?: string | MarkdownString; - /** - * Whether this test item can be run by providing it in the - * {@link TestRunRequest.tests} array. Defaults to `true`. - */ - runnable: boolean; - - /** - * Whether this test item can be debugged by providing it in the - * {@link TestRunRequest.tests} array. Defaults to `false`. - */ - debuggable: boolean; - /** * Marks the test as outdated. This can happen as a result of file changes, * for example. In "auto run" mode, tests that are outdated will be @@ -2119,12 +2236,8 @@ declare module 'vscode' { * * Extensions should generally not override this method. */ + // todo@api still unsure about this invalidateResults(): void; - - /** - * Removes the test and its children from the tree. - */ - dispose(): void; } /** @@ -2144,6 +2257,7 @@ declare module 'vscode' { // Test run has been skipped Skipped = 5, // Test run failed for some other reason (compilation error, timeout, etc) + // todo@api could I just use `Skipped` and TestItem#error? Errored = 6 } @@ -2914,4 +3028,95 @@ declare module 'vscode' { export type DetailedCoverage = StatementCoverage | FunctionCoverage; //#endregion + + + //#region https://github.com/microsoft/vscode/issues/15533 --- Type hierarchy --- @eskibear + export class TypeHierarchyItem { + /** + * The name of this item. + */ + name: string; + /** + * The kind of this item. + */ + kind: SymbolKind; + /** + * Tags for this item. + */ + tags?: ReadonlyArray; + /** + * More detail for this item, e.g. the signature of a function. + */ + detail?: string; + /** + * The resource identifier of this item. + */ + uri: Uri; + /** + * The range enclosing this symbol not including leading/trailing whitespace + * but everything else, e.g. comments and code. + */ + range: Range; + /** + * The range that should be selected and revealed when this symbol is being + * picked, e.g. the name of a function. Must be contained by the + * [`range`](#TypeHierarchyItem.range). + */ + selectionRange: Range; + + constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); + } + + export interface TypeHierarchyProvider { + + /** + * Bootstraps type hierarchy by returning the item that is denoted by the given document + * and position. This item will be used as entry into the type graph. Providers should + * return `undefined` or `null` when there is no item at the given location. + * + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param token A cancellation token. + * @returns A type hierarchy item or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + prepareTypeHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + + /** + * Provide all supertypes for an item, e.g all types from which a type is derived/inherited. In graph terms this describes directed + * and annotated edges inside the type graph, e.g the given item is the starting node and the result is the nodes + * that can be reached. + * + * @param item The hierarchy item for which super types should be computed. + * @param token A cancellation token. + * @returns A set of supertypes or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideTypeHierarchySupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; + + /** + * Provide all subtypes for an item, e.g all types which are derived/inherited from the given item. In + * graph terms this describes directed and annotated edges inside the type graph, e.g the given item is the starting + * node and the result is the nodes that can be reached. + * + * @param item The hierarchy item for which subtypes should be computed. + * @param token A cancellation token. + * @returns A set of subtypes or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideTypeHierarchySubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a type hierarchy provider. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A type hierarchy provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerTypeHierarchyProvider(selector: DocumentSelector, provider: TypeHierarchyProvider): Disposable; + } + //#endregion + } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index bd011ed66c3ef4fef9b340a8738e0ef4a3eae532..45ee8e795fb7ec9f44346e093dbc1df787d2d67a 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -20,6 +20,7 @@ import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { mixin } from 'vs/base/common/objects'; import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; @@ -147,6 +148,13 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return data as callh.CallHierarchyItem; } + private static _reviveTypeHierarchyItemDto(data: ITypeHierarchyItemDto | undefined): typeh.TypeHierarchyItem { + if (data) { + data.uri = URI.revive(data.uri); + } + return data as typeh.TypeHierarchyItem; + } + //#endregion // --- outline @@ -773,6 +781,43 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } + // --- type hierarchy + + $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, typeh.TypeHierarchyProviderRegistry.register(selector, { + + prepareTypeHierarchy: async (document, position, token) => { + const items = await this._proxy.$prepareTypeHierarchy(handle, document.uri, position, token); + if (!items) { + return undefined; + } + return { + dispose: () => { + for (const item of items) { + this._proxy.$releaseTypeHierarchy(handle, item._sessionId); + } + }, + roots: items.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto) + }; + }, + + provideSupertypes: async (item, token) => { + const supertypes = await this._proxy.$provideTypeHierarchySupertypes(handle, item._sessionId, item._itemId, token); + if (!supertypes) { + return supertypes; + } + return supertypes.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto); + }, + provideSubtypes: async (item, token) => { + const subtypes = await this._proxy.$provideTypeHierarchySubtypes(handle, item._sessionId, item._itemId, token); + if (!subtypes) { + return subtypes; + } + return subtypes.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto); + } + })); + } + } export class MainThreadDocumentSemanticTokensProvider implements modes.DocumentSemanticTokensProvider { diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts index c8fd0498b0010830e4082c9adc689f588d3d55a0..78df4994603aa4be8d7ee22456422e6fac8c2a55 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -190,8 +190,14 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape $trySetSelections(id: string, ranges: ICellRange[]): void { const editor = this._notebookEditorService.getNotebookEditor(id); - if (editor) { - editor.setSelections(ranges); + if (!editor) { + return; + } + + editor.setSelections(ranges); + + if (ranges.length) { + editor.setFocus({ start: ranges[0].start, end: ranges[0].start + 1 }); } } } diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index 960f38b305e1c38196e69dc97e2d26839a6560ba..63e4d98e6d72a1e232717f63cadfd78d8bbfbb58 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -5,18 +5,19 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TestResultState } from 'vs/workbench/api/common/extHostTypes'; import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; -import { ExtensionRunTestsRequest, ITestItem, ITestMessage, ITestRunTask, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ExtensionRunTestsRequest, ITestItem, ITestMessage, ITestRunConfiguration, ITestRunTask, ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; -import { ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService'; +import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { ExtHostContext, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; const reviveDiff = (diff: TestsDiff) => { @@ -37,11 +38,16 @@ const reviveDiff = (diff: TestsDiff) => { export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider { private readonly proxy: ExtHostTestingShape; private readonly diffListener = this._register(new MutableDisposable()); - private readonly testProviderRegistrations = new Map(); + private readonly testProviderRegistrations = new Map; + disposable: IDisposable + }>(); constructor( extHostContext: IExtHostContext, @ITestService private readonly testService: ITestService, + @ITestConfigurationService private readonly testConfiguration: ITestConfigurationService, @ITestResultService private readonly resultService: ITestResultService, ) { super(); @@ -65,6 +71,30 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh })); } + /** + * @inheritdoc + */ + $publishTestRunConfig(config: ITestRunConfiguration): void { + const controller = this.testProviderRegistrations.get(config.controllerId); + if (controller) { + this.testConfiguration.addConfiguration(controller.instance, config); + } + } + + /** + * @inheritdoc + */ + $updateTestRunConfig(controllerId: string, configId: number, update: Partial): void { + this.testConfiguration.updateConfiguration(controllerId, configId, update); + } + + /** + * @inheritdoc + */ + $removeTestRunConfig(controllerId: string, configId: number): void { + this.testConfiguration.removeConfiguration(controllerId, configId); + } + /** * @inheritdoc */ @@ -157,21 +187,44 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh /** * @inheritdoc */ - public $registerTestController(controllerId: string) { - const disposable = this.testService.registerTestController(controllerId, { + public $registerTestController(controllerId: string, labelStr: string) { + const disposable = new DisposableStore(); + const label = new MutableObservableValue(labelStr); + const controller: IMainThreadTestController = { + id: controllerId, + label, + configureRunConfig: id => this.proxy.$configureRunConfig(controllerId, id), runTests: (req, token) => this.proxy.$runControllerTests(req, token), expandTest: (src, levels) => this.proxy.$expandTest(src, isFinite(levels) ? levels : -1), + }; + + + disposable.add(toDisposable(() => this.testConfiguration.removeConfiguration(controllerId))); + disposable.add(this.testService.registerTestController(controllerId, controller)); + + this.testProviderRegistrations.set(controllerId, { + instance: controller, + label, + disposable }); + } - this.testProviderRegistrations.set(controllerId, disposable); + /** + * @inheritdoc + */ + public $updateControllerLabel(controllerId: string, label: string) { + const controller = this.testProviderRegistrations.get(controllerId); + if (controller) { + controller.label.value = label; + } } /** * @inheritdoc */ - public $unregisterTestController(id: string) { - this.testProviderRegistrations.get(id)?.dispose(); - this.testProviderRegistrations.delete(id); + public $unregisterTestController(controllerId: string) { + this.testProviderRegistrations.get(controllerId)?.disposable.dispose(); + this.testProviderRegistrations.delete(controllerId); } /** @@ -197,15 +250,15 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh this.testService.publishDiff(controllerId, diff); } - public async $runTests(req: RunTestsRequest, token: CancellationToken): Promise { - const result = await this.testService.runTests(req, token); + public async $runTests(req: ResolvedTestRunRequest, token: CancellationToken): Promise { + const result = await this.testService.runResolvedTests(req, token); return result.id; } public override dispose() { super.dispose(); for (const subscription of this.testProviderRegistrations.values()) { - subscription.dispose(); + subscription.disposable.dispose(); } this.testProviderRegistrations.clear(); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index dd7808fb806e1dae298d61550ffdbbc3f0d67124..dc6d45f3483ffdaed6166a03c24f87d3feb84c62 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -342,9 +342,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I : extHostTypes.ExtensionKind.UI; const test: typeof vscode.test = { - createTestController(provider) { + createTestController(provider, label) { checkProposedApiEnabled(extension); - return extHostTesting.createTestController(provider); + return extHostTesting.createTestController(provider, label); + }, + createTestItem(id, label, uri) { + checkProposedApiEnabled(extension); + return extHostTesting.createTestItem(id, label, uri); }, createTestObserver() { checkProposedApiEnabled(extension); @@ -499,6 +503,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerInlayHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable { checkProposedApiEnabled(extension); return extHostLanguageFeatures.registerInlayHintsProvider(extension, selector, provider); + }, + registerTypeHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.TypeHierarchyProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerTypeHierarchyProvider(extension, selector, provider); } }; @@ -1215,7 +1223,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I SymbolKind: extHostTypes.SymbolKind, SymbolTag: extHostTypes.SymbolTag, Task: extHostTypes.Task, + Task2: extHostTypes.Task, TaskGroup: extHostTypes.TaskGroup, + TaskGroup2: extHostTypes.TaskGroup, TaskPanelKind: extHostTypes.TaskPanelKind, TaskRevealKind: extHostTypes.TaskRevealKind, TaskScope: extHostTypes.TaskScope, @@ -1232,6 +1242,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ThemeIcon: extHostTypes.ThemeIcon, TreeItem: extHostTypes.TreeItem, TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, + TypeHierarchyItem: extHostTypes.TypeHierarchyItem, UIKind: UIKind, Uri: URI, ViewColumn: extHostTypes.ViewColumn, @@ -1261,6 +1272,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TestResultState: extHostTypes.TestResultState, TestRunRequest: extHostTypes.TestRunRequest, TestMessage: extHostTypes.TestMessage, + TestRunConfigurationGroup: extHostTypes.TestRunConfigurationGroup, TextSearchCompleteMessageType: TextSearchCompleteMessageType, TestMessageSeverity: extHostTypes.TestMessageSeverity, CoveredCount: extHostTypes.CoveredCount, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ebd88795f41f3ea0447cdbec6d0e44602c04ccc0..713eb47dece97f20ab970a9fa9c3af0ad32fc2fc 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -57,8 +57,9 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { ExtensionRunTestsRequest, ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, RunTestForControllerRequest, RunTestsRequest, ITestIdWithSrc, TestsDiff, IFileCoverage, CoverageDetails } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ExtensionRunTestsRequest, ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, RunTestForControllerRequest, ResolvedTestRunRequest, ITestIdWithSrc, TestsDiff, IFileCoverage, CoverageDetails, ITestRunConfiguration } from 'vs/workbench/contrib/testing/common/testCollection'; import { InternalTimelineOptions, Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ActivationKind, ExtensionHostKind, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -77,6 +78,7 @@ export interface IEnvironment { globalStorageHome: URI; workspaceStorageHome: URI; useHostProxy?: boolean; + skipWorkspaceStorageLock?: boolean; } export interface IStaticWorkspaceData { @@ -413,6 +415,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $emitFoldingRangeEvent(eventHandle: number, event?: any): void; $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } @@ -1627,6 +1630,8 @@ export interface IInlineValueContextDto { stoppedLocation: IRange; } +export type ITypeHierarchyItemDto = Dto; + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1677,6 +1682,10 @@ export interface ExtHostLanguageFeaturesShape { $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseCallHierarchy(handle: number, sessionId: string): void; $setWordDefinitions(wordDefinitions: ILanguageWordDefinitionDto[]): void; + $prepareTypeHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $releaseTypeHierarchy(handle: number, sessionId: string): void; } export interface ExtHostQuickOpenShape { @@ -2087,11 +2096,17 @@ export interface ExtHostTestingShape { * Requires file coverage to have been previously requested via $provideFileCoverage. */ $resolveFileCoverage(runId: string, taskId: string, fileIndex: number, token: CancellationToken): Promise; + /** Configures a test run config. */ + $configureRunConfig(controllerId: string, configId: number): void; } export interface MainThreadTestingShape { - /** Registeres that there's a test controller with the given ID */ - $registerTestController(controllerId: string): void; + // --- test lifecycle: + + /** Registers that there's a test controller with the given ID */ + $registerTestController(controllerId: string, label: string): void; + /** Updates the label of an existing test controller. */ + $updateControllerLabel(controllerId: string, label: string): void; /** Diposes of the test controller with the given ID */ $unregisterTestController(controllerId: string): void; /** Requests tests published to VS Code. */ @@ -2100,11 +2115,21 @@ export interface MainThreadTestingShape { $unsubscribeFromDiffs(): void; /** Publishes that new tests were available on the given source. */ $publishDiff(controllerId: string, diff: TestsDiff): void; - /** Request by an extension to run tests. */ - $runTests(req: RunTestsRequest, token: CancellationToken): Promise; + + // --- test run configurations: + + /** Called when a new test run configuration is available */ + $publishTestRunConfig(config: ITestRunConfiguration): void; + /** Updates an existing test run configuration */ + $updateTestRunConfig(controllerId: string, configId: number, update: Partial): void; + /** Removes a previously-published test run config */ + $removeTestRunConfig(controllerId: string, configId: number): void; + // --- test run handling: + /** Request by an extension to run tests. */ + $runTests(req: ResolvedTestRunRequest, token: CancellationToken): Promise; /** * Adds tests to the run. The tests are given in descending depth. The first * item will be a previously-known test, or a test root. diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 6aa039d2b3ca20f61f3782e7b8ae4273752efd60..30e14491adebbdaf549d5f088d10e3284d6fa6d5 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import type * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto, ITypeHierarchyItemDto } from 'vs/workbench/api/common/extHost.protocol'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -406,6 +406,22 @@ const newCommands: ApiCommand[] = [ ], ApiCommandResult.Void ), + // --- type hierarchy + new ApiCommand( + 'vscode.prepareTypeHierarchy', '_executePrepareTypeHierarchy', 'Prepare type hierarchy at a position inside a document', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult('A TypeHierarchyItem or undefined', v => v.map(typeConverters.TypeHierarchyItem.to)) + ), + new ApiCommand( + 'vscode.provideSupertypes', '_executeProvideSupertypes', 'Compute supertypes for an item', + [ApiCommandArgument.TypeHierarchyItem], + new ApiCommandResult('A TypeHierarchyItem or undefined', v => v.map(typeConverters.TypeHierarchyItem.to)) + ), + new ApiCommand( + 'vscode.provideSubtypes', '_executeProvideSubtypes', 'Compute subtypes for an item', + [ApiCommandArgument.TypeHierarchyItem], + new ApiCommandResult('A TypeHierarchyItem or undefined', v => v.map(typeConverters.TypeHierarchyItem.to)) + ), ]; //#endregion diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 14cb4899824077ab97bf2fa01aae880a375e13e7..399a42fdbfd99c40948b019154b7c8a7a6f255a6 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -369,7 +369,8 @@ export class ApiCommandArgument { static readonly Number = new ApiCommandArgument('number', '', v => typeof v === 'number', v => v); static readonly String = new ApiCommandArgument('string', '', v => typeof v === 'string', v => v); - static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.to); + static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.from); + static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from); constructor( readonly name: string, diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index d94cfc164de15072453516c72c37c55ae26474ae..b021f9c03f721ff822b7746f1ae087bf01e5fd01 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1408,17 +1408,7 @@ class CallHierarchyAdapter { private _cacheAndConvertItem(sessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { const map = this._cache.get(sessionId)!; - const dto: extHostProtocol.ICallHierarchyItemDto = { - _sessionId: sessionId, - _itemId: map.size.toString(36), - name: item.name, - detail: item.detail, - kind: typeConvert.SymbolKind.from(item.kind), - uri: item.uri, - range: typeConvert.Range.from(item.range), - selectionRange: typeConvert.Range.from(item.selectionRange), - tags: item.tags?.map(typeConvert.SymbolTag.from) - }; + const dto = typeConvert.CallHierarchyItem.from(item, sessionId, map.size.toString(36)); map.set(dto._itemId, item); return dto; } @@ -1429,12 +1419,86 @@ class CallHierarchyAdapter { } } +class TypeHierarchyAdapter { + + private readonly _idPool = new IdGenerator(''); + private readonly _cache = new Map>(); + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.TypeHierarchyProvider + ) { } + + async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { + const doc = this._documents.getDocument(uri); + const pos = typeConvert.Position.to(position); + + const items = await this._provider.prepareTypeHierarchy(doc, pos, token); + if (!items) { + return undefined; + } + + const sessionId = this._idPool.nextId(); + this._cache.set(sessionId, new Map()); + + if (Array.isArray(items)) { + return items.map(item => this._cacheAndConvertItem(sessionId, item)); + } else { + return [this._cacheAndConvertItem(sessionId, items)]; + } + } + + async provideSupertypes(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing type hierarchy item'); + } + const supertypes = await this._provider.provideTypeHierarchySupertypes(item, token); + if (!supertypes) { + return undefined; + } + return supertypes.map(supertype => { + return this._cacheAndConvertItem(sessionId, supertype); + }); + } + + async provideSubtypes(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing type hierarchy item'); + } + const subtypes = await this._provider.provideTypeHierarchySubtypes(item, token); + if (!subtypes) { + return undefined; + } + return subtypes.map(subtype => { + return this._cacheAndConvertItem(sessionId, subtype); + }); + } + + releaseSession(sessionId: string): void { + this._cache.delete(sessionId); + } + + private _cacheAndConvertItem(sessionId: string, item: vscode.TypeHierarchyItem): extHostProtocol.ITypeHierarchyItemDto { + const map = this._cache.get(sessionId)!; + const dto = typeConvert.TypeHierarchyItem.from(item, sessionId, map.size.toString(36)); + map.set(dto._itemId, item); + return dto; + } + + private _itemFromCache(sessionId: string, itemId: string): vscode.TypeHierarchyItem | undefined { + const map = this._cache.get(sessionId); + return map?.get(itemId); + } +} type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter - | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter + | SelectionRangeAdapter | CallHierarchyAdapter | TypeHierarchyAdapter + | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter; @@ -2043,6 +2107,29 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined); } + // --- type hierarchy + registerTypeHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.TypeHierarchyProvider): vscode.Disposable { + const handle = this._addNewAdapter(new TypeHierarchyAdapter(this._documents, provider), extension); + this._proxy.$registerTypeHierarchyProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + $prepareTypeHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined); + } + + $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, TypeHierarchyAdapter, adapter => adapter.provideSupertypes(sessionId, itemId, token), undefined); + } + + $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, TypeHierarchyAdapter, adapter => adapter.provideSubtypes(sessionId, itemId, token), undefined); + } + + $releaseTypeHierarchy(handle: number, sessionId: string): void { + this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined); + } + // --- configuration private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto { diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 8f5d4a159774f50e710a9ccdf1e424465393e86a..90f8cf7df3922edd05e0d247f4a287acaf69ed20 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -8,7 +8,7 @@ import { asPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; - +import * as Objects from 'vs/base/common/objects'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspaceProvider, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import type * as vscode from 'vscode'; @@ -213,6 +213,14 @@ export namespace TaskHandleDTO { }; } } +export namespace TaskGroupDTO { + export function from(value: vscode.TaskGroup2): tasks.TaskGroupDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + return { _id: value.id, isDefault: value.isDefault }; + } +} export namespace TaskDTO { export function fromMany(tasks: vscode.Task[], extension: IExtensionDescription): tasks.TaskDTO[] { @@ -257,7 +265,6 @@ export namespace TaskDTO { if (!definition || !scope) { return undefined; } - const group = (value.group as types.TaskGroup) ? (value.group as types.TaskGroup).id : undefined; const result: tasks.TaskDTO = { _id: (value as types.Task)._id!, definition, @@ -269,7 +276,7 @@ export namespace TaskDTO { }, execution: execution!, isBackground: value.isBackground, - group: group, + group: TaskGroupDTO.from(value.group as vscode.TaskGroup2), presentationOptions: TaskPresentationOptionsDTO.from(value.presentationOptions), problemMatchers: value.problemMatchers, hasDefinedMatchers: (value as types.Task).hasDefinedMatchers, @@ -311,7 +318,13 @@ export namespace TaskDTO { result.isBackground = value.isBackground; } if (value.group !== undefined) { - result.group = types.TaskGroup.from(value.group); + result.group = types.TaskGroup.from(value.group._id); + if (result.group) { + result.group = Objects.deepClone(result.group); + if (value.group.isDefault) { + result.group.isDefault = value.group.isDefault; + } + } } if (value.presentationOptions) { result.presentationOptions = TaskPresentationOptionsDTO.to(value.presentationOptions)!; diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index cb6fb32b626b4825e67de58e640494b0e6073f69..4cc68560b825c86ad7c016117f5001aa6207e2d6 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mapFind } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; +import { hash } from 'vs/base/common/hash'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshalling'; @@ -18,18 +18,22 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; -import { TestItemImpl } from 'vs/workbench/api/common/extHostTypes'; +import { TestRunConfigurationGroup, TestRunRequest } from 'vs/workbench/api/common/extHostTypes'; import { SingleUseTestCollection, TestPosition } from 'vs/workbench/contrib/testing/common/ownedTestCollection'; -import { AbstractIncrementalTestCollection, CoverageDetails, IFileCoverage, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestIdWithSrc, ITestItem, RunTestForControllerRequest, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { AbstractIncrementalTestCollection, CoverageDetails, IFileCoverage, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestIdWithSrc, ITestItem, RunTestForControllerRequest, TestRunConfigurationBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; import type * as vscode from 'vscode'; +interface ControllerInfo { + controller: vscode.TestController, + configurations: Map, + collection: SingleUseTestCollection, +} + export class ExtHostTesting implements ExtHostTestingShape { private readonly resultsChangedEmitter = new Emitter(); - private readonly controllers = new Map(); + private readonly controllers = new Map(); private readonly proxy: MainThreadTestingShape; private readonly runTracker: TestRunCoordinator; private readonly observer: TestObservers; @@ -51,25 +55,40 @@ export class ExtHostTesting implements ExtHostTestingShape { /** * Implements vscode.test.registerTestProvider */ - public createTestController(controllerId: string): vscode.TestController { + public createTestController(controllerId: string, label: string): vscode.TestController { const disposable = new DisposableStore(); const collection = disposable.add(new SingleUseTestCollection(controllerId)); const initialExpand = disposable.add(new RunOnceScheduler(() => collection.expand(collection.root.id, 0), 0)); + const configurations = new Map(); + const proxy = this.proxy; const controller: vscode.TestController = { - root: collection.root, + items: collection.root.children, + get label() { + return label; + }, + set label(value: string) { + label = value; + collection.root.label = value; + proxy.$updateControllerLabel(controllerId, label); + }, get id() { return controllerId; }, - createTestRun: (request, name, persist = true) => { - return this.runTracker.createTestRun(controllerId, request, name, persist); - }, - createTestItem(id: string, label: string, parent: vscode.TestItem, uri: vscode.Uri, data?: unknown) { - if (!(parent instanceof TestItemImpl)) { - throw new Error(`The "parent" passed in for TestItem ${id} is invalid`); + createRunConfiguration: (label, group, runHandler, isDefault) => { + // Derive the config ID from a hash so that the same config will tend + // to have the same hashes, allowing re-run requests to work across reloads. + let configId = hash(label); + while (configurations.has(configId)) { + configId++; } - return new TestItemImpl(id, label, uri, data, parent); + const config = new TestRunConfigurationImpl(this.proxy, controllerId, configId, label, group, runHandler, isDefault); + configurations.set(configId, config); + return config; + }, + createTestRun: (request, name, persist = true) => { + return this.runTracker.createTestRun(controllerId, collection, request, name, persist); }, set resolveChildrenHandler(fn) { collection.resolveHandler = fn; @@ -85,17 +104,28 @@ export class ExtHostTesting implements ExtHostTestingShape { }, }; - this.proxy.$registerTestController(controllerId); - disposable.add(toDisposable(() => this.proxy.$unregisterTestController(controllerId))); + // back compat: + (controller as any).createTestITem = this.createTestItem.bind(this); + + proxy.$registerTestController(controllerId, label); + disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId))); - this.controllers.set(controllerId, { controller, collection }); + const info: ControllerInfo = { controller, collection, configurations }; + this.controllers.set(controllerId, info); disposable.add(toDisposable(() => this.controllers.delete(controllerId))); - disposable.add(collection.onDidGenerateDiff(diff => this.proxy.$publishDiff(controllerId, diff))); + disposable.add(collection.onDidGenerateDiff(diff => proxy.$publishDiff(controllerId, diff))); return controller; } + /** + * Implements vscode.test.createTestItem + */ + public createTestItem(id: string, label: string, uri?: vscode.Uri) { + return new TestItemImpl(id, label, uri); + } + /** * Implements vscode.test.createTestObserver */ @@ -108,16 +138,24 @@ export class ExtHostTesting implements ExtHostTestingShape { * Implements vscode.test.runTests */ public async runTests(req: vscode.TestRunRequest, token = CancellationToken.None) { - const testListToProviders = (tests: ReadonlyArray) => - tests - .map(this.getInternalTestForReference, this) - .filter(isDefined) - .map(t => ({ controllerId: t.controllerId, testId: t.item.extId })); + const config = tryGetConfigFromTestRunReq(req); + if (!config) { + throw new Error('The request passed to `vscode.test.runTests` must include a configuration'); + } + + const controller = this.controllers.get(config.controllerId); + if (!controller) { + throw new Error('Controller not found'); + } await this.proxy.$runTests({ - exclude: req.exclude ? testListToProviders(req.exclude).map(t => t.testId) : undefined, - tests: testListToProviders(req.tests), - debug: req.debug + targets: [{ + testIds: req.include?.map(t => t.id) ?? [controller.collection.root.id], + profileGroup: configGroupToBitset[config.group], + profileId: config.configId, + controllerId: config.controllerId, + }], + exclude: req.exclude?.map(t => ({ testId: t.id, controllerId: config.controllerId })), }, token); } @@ -135,6 +173,11 @@ export class ExtHostTesting implements ExtHostTestingShape { return Iterable.find(this.runTracker.trackers, t => t.id === runId)?.getCoverage(taskId)?.resolveFileCoverage(fileIndex, token) ?? Promise.resolve([]); } + /** @inheritdoc */ + $configureRunConfig(controllerId: string, configId: number) { + this.controllers.get(controllerId)?.configurations.get(configId)?.configureHandler?.(); + } + /** * Updates test results shown to extensions. * @override @@ -182,7 +225,12 @@ export class ExtHostTesting implements ExtHostTestingShape { return; } - const { controller, collection } = lookup; + const { collection, configurations } = lookup; + const configuration = configurations.get(req.configId); + if (!configuration) { + return; + } + const includeTests = req.testIds .map((testId) => collection.tree.get(testId)) .filter(isDefined); @@ -198,16 +246,20 @@ export class ExtHostTesting implements ExtHostTestingShape { return; } - const publicReq: vscode.TestRunRequest = { - tests: includeTests.map(t => t.actual), - exclude: excludeTests.map(t => t.actual), - debug: req.debug, - }; + const publicReq = new TestRunRequest( + includeTests.map(t => t.actual), + excludeTests.map(t => t.actual), + configuration, + ); - const tracker = this.runTracker.prepareForMainThreadTestRun(publicReq, TestRunDto.fromInternal(req), token); + const tracker = this.runTracker.prepareForMainThreadTestRun( + publicReq, + TestRunDto.fromInternal(req, lookup.collection), + token, + ); try { - await controller.runHandler?.(publicReq, token); + await configuration.runHandler(publicReq, token); } finally { if (tracker.isRunning && !token.isCancellationRequested) { await Event.toPromise(tracker.onEnd); @@ -227,14 +279,6 @@ export class ExtHostTesting implements ExtHostTestingShape { this.runTracker.cancelRunById(runId); } } - - /** - * Gets the internal test item associated with the reference from the extension. - */ - private getInternalTestForReference(test: vscode.TestItem) { - return mapFind(this.controllers.values(), ({ collection }) => collection.getTestByReference(test)) - ?? this.observer.getMirroredTestDataByReference(test); - } } class TestRunTracker extends Disposable { @@ -348,7 +392,7 @@ export class TestRunCoordinator { /** * Implements the public `createTestRun` API. */ - public createTestRun(controllerId: string, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { + public createTestRun(controllerId: string, collection: SingleUseTestCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { const existing = this.tracked.get(request); if (existing) { return existing.createRun(name); @@ -356,12 +400,14 @@ export class TestRunCoordinator { // If there is not an existing tracked extension for the request, start // a new, detached session. - const dto = TestRunDto.fromPublic(controllerId, request); + const dto = TestRunDto.fromPublic(controllerId, collection, request); + const config = tryGetConfigFromTestRunReq(request); this.proxy.$startedExtensionTestRun({ - debug: request.debug, + controllerId, + config: config && { group: configGroupToBitset[config.group], id: config.configId }, exclude: request.exclude?.map(t => t.id) ?? [], id: dto.id, - tests: request.tests.map(t => t.id), + include: request.include?.map(t => t.id) ?? [collection.root.id], persist }); @@ -378,42 +424,57 @@ export class TestRunCoordinator { } } +const tryGetConfigFromTestRunReq = (request: vscode.TestRunRequest) => { + if (!request.configuration) { + return undefined; + } + + if (!(request.configuration instanceof TestRunConfigurationImpl)) { + throw new Error(`TestRunRequest.configuration is not an instance created from TestController.createRunConfiguration`); + } + + return request.configuration; +}; + export class TestRunDto { - public static fromPublic(controllerId: string, request: vscode.TestRunRequest) { + public static fromPublic(controllerId: string, collection: SingleUseTestCollection, request: vscode.TestRunRequest) { return new TestRunDto( controllerId, generateUuid(), - new Set(request.tests.map(t => t.id)), + request.include && new Set(request.include.map(t => t.id)), new Set(request.exclude?.map(t => t.id) ?? Iterable.empty()), + collection, ); } - public static fromInternal(request: RunTestForControllerRequest) { + public static fromInternal(request: RunTestForControllerRequest, collection: SingleUseTestCollection) { return new TestRunDto( request.controllerId, request.runId, - new Set(request.testIds), + request.testIds.includes(collection.root.id) ? undefined : new Set(request.testIds), new Set(request.excludeExtIds), + collection, ); } constructor( public readonly controllerId: string, public readonly id: string, - private readonly include: ReadonlySet, + private readonly include: ReadonlySet | undefined, private readonly exclude: ReadonlySet, + public readonly colllection: SingleUseTestCollection, ) { } public isIncluded(test: vscode.TestItem) { for (let t: vscode.TestItem | undefined = test; t; t = t.parent) { - if (this.include.has(t.id)) { + if (this.include?.has(t.id)) { return true; } else if (this.exclude.has(t.id)) { return false; } } - return false; + return this.include === undefined; // default to true if running all tests with include=undefined } } @@ -562,6 +623,12 @@ class TestRunImpl implements vscode.TestRun { test = test.parent; } + const root = this.#req.colllection.root; + if (!sent.has(root.id)) { + sent.add(root.id); + chain.unshift(Convert.TestItem.from(root)); + } + this.#proxy.$addTestsToRun(this.#req.controllerId, this.#req.id, chain); } } @@ -748,3 +815,77 @@ class TestObservers { return { observers: 0, tests, }; } } + +export class TestRunConfigurationImpl implements vscode.TestRunConfiguration { + readonly #proxy: MainThreadTestingShape; + private _configureHandler?: (() => void); + + public get label() { + return this._label; + } + + public set label(label: string) { + if (label !== this._label) { + this._label = label; + this.#proxy.$updateTestRunConfig(this.controllerId, this.configId, { label }); + } + } + + public get isDefault() { + return this._isDefault; + } + + public set isDefault(isDefault: boolean) { + if (isDefault !== this._isDefault) { + this._isDefault = isDefault; + this.#proxy.$updateTestRunConfig(this.controllerId, this.configId, { isDefault }); + } + } + + public get configureHandler() { + return this._configureHandler; + } + + public set configureHandler(handler: undefined | (() => void)) { + if (handler !== this._configureHandler) { + this._configureHandler = handler; + this.#proxy.$updateTestRunConfig(this.controllerId, this.configId, { hasConfigurationHandler: !!handler }); + } + } + + constructor( + proxy: MainThreadTestingShape, + public readonly controllerId: string, + public readonly configId: number, + private _label: string, + public readonly group: vscode.TestRunConfigurationGroup, + public runHandler: vscode.TestRunHandler, + private _isDefault = false, + ) { + this.#proxy = proxy; + + const groupBitset = configGroupToBitset[group]; + if (typeof groupBitset !== 'number') { + throw new Error(`Unknown TestRunConfiguration.group ${group}`); + } + + this.#proxy.$publishTestRunConfig({ + profileId: configId, + controllerId, + label: _label, + group: groupBitset, + isDefault: _isDefault, + hasConfigurationHandler: false, + }); + } + + dispose(): void { + this.#proxy.$removeTestRunConfig(this.controllerId, this.configId); + } +} + +const configGroupToBitset: { [K in TestRunConfigurationGroup]: TestRunConfigurationBitset } = { + [TestRunConfigurationGroup.Coverage]: TestRunConfigurationBitset.Coverage, + [TestRunConfigurationGroup.Debug]: TestRunConfigurationBitset.Debug, + [TestRunConfigurationGroup.Run]: TestRunConfigurationBitset.Run, +}; diff --git a/src/vs/workbench/api/common/extHostTestingPrivateApi.ts b/src/vs/workbench/api/common/extHostTestingPrivateApi.ts index 9d99f15d52580e3b3f973efbd894ed2875601b81..996f47b8c066041fc706ec114a2ee27c753e15d3 100644 --- a/src/vs/workbench/api/common/extHostTestingPrivateApi.ts +++ b/src/vs/workbench/api/common/extHostTestingPrivateApi.ts @@ -3,26 +3,50 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; -import { TestItemImpl } from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; -export const enum ExtHostTestItemEventType { - NewChild, - Disposed, +export const enum ExtHostTestItemEventOp { + Upsert, + RemoveChild, Invalidated, SetProp, + Bulk, +} + +export interface ITestItemUpsertChild { + op: ExtHostTestItemEventOp.Upsert; + item: TestItemImpl; +} + +export interface ITestItemRemoveChild { + op: ExtHostTestItemEventOp.RemoveChild; + id: string; +} + +export interface ITestItemInvalidated { + op: ExtHostTestItemEventOp.Invalidated; +} + +export interface ITestItemSetProp { + op: ExtHostTestItemEventOp.SetProp; + key: keyof vscode.TestItem; + value: any; +} +export interface ITestItemBulkReplace { + op: ExtHostTestItemEventOp.Bulk; + ops: (ITestItemUpsertChild | ITestItemRemoveChild)[]; } export type ExtHostTestItemEvent = - | [evt: ExtHostTestItemEventType.NewChild, item: TestItemImpl] - | [evt: ExtHostTestItemEventType.Disposed] - | [evt: ExtHostTestItemEventType.Invalidated] - | [evt: ExtHostTestItemEventType.SetProp, key: keyof vscode.TestItem, value: any]; + | ITestItemUpsertChild + | ITestItemRemoveChild + | ITestItemInvalidated + | ITestItemSetProp + | ITestItemBulkReplace; export interface IExtHostTestItemApi { - children: Map; - bus: Emitter; + parent?: TestItemImpl; + listener?: (evt: ExtHostTestItemEvent) => void; } const eventPrivateApis = new WeakMap(); @@ -35,9 +59,216 @@ const eventPrivateApis = new WeakMap(); export const getPrivateApiFor = (impl: TestItemImpl) => { let api = eventPrivateApis.get(impl); if (!api) { - api = { children: new Map(), bus: new Emitter() }; + api = {}; eventPrivateApis.set(impl, api); } return api; }; + +const testItemPropAccessor = ( + api: IExtHostTestItemApi, + key: K, + defaultValue: vscode.TestItem[K], + equals: (a: vscode.TestItem[K], b: vscode.TestItem[K]) => boolean +) => { + let value = defaultValue; + return { + enumerable: true, + configurable: false, + get() { + return value; + }, + set(newValue: vscode.TestItem[K]) { + if (!equals(value, newValue)) { + value = newValue; + api.listener?.({ op: ExtHostTestItemEventOp.SetProp, key, value: newValue }); + } + }, + }; +}; + +type WritableProps = Pick; + +const strictEqualComparator = (a: T, b: T) => a === b; + +const propComparators: { [K in keyof Required]: (a: vscode.TestItem[K], b: vscode.TestItem[K]) => boolean } = { + range: (a, b) => { + if (a === b) { return true; } + if (!a || !b) { return false; } + return a.isEqual(b); + }, + label: strictEqualComparator, + description: strictEqualComparator, + busy: strictEqualComparator, + error: strictEqualComparator, + canResolveChildren: strictEqualComparator +}; + +const writablePropKeys = Object.keys(propComparators) as (keyof Required)[]; + +const makePropDescriptors = (api: IExtHostTestItemApi, label: string): { [K in keyof Required]: PropertyDescriptor } => ({ + range: testItemPropAccessor(api, 'range', undefined, propComparators.range), + label: testItemPropAccessor(api, 'label', label, propComparators.label), + description: testItemPropAccessor(api, 'description', undefined, propComparators.description), + canResolveChildren: testItemPropAccessor(api, 'canResolveChildren', false, propComparators.canResolveChildren), + busy: testItemPropAccessor(api, 'busy', false, propComparators.busy), + error: testItemPropAccessor(api, 'error', undefined, propComparators.error), +}); + +/** + * Returns a partial test item containing the writable properties in B that + * are different from A. + */ +export const diffTestItems = (a: vscode.TestItem, b: vscode.TestItem) => { + const output = new Map(); + for (const key of writablePropKeys) { + const cmp = propComparators[key] as (a: unknown, b: unknown) => boolean; + if (!cmp(a[key], b[key])) { + output.set(key, b[key]); + } + } + + return output; +}; + +export class DuplicateTestItemError extends Error { + constructor(id: string) { + super(`Attempted to insert a duplicate test item ID ${id}`); + } +} + +export class InvalidTestItemError extends Error { + constructor(id: string) { + super(`TestItem with ID "${id}" is invalid. Make sure to create it from the createTestItem method.`); + } +} + +export const createTestItemCollection = (owningItem: TestItemImpl): + vscode.TestItemCollection & { toJSON(): readonly vscode.TestItem[] } => { + const api = getPrivateApiFor(owningItem); + let all: readonly TestItemImpl[] | undefined; + let mapped = new Map(); + + return { + /** @inheritdoc */ + get all() { + if (!all) { + all = Object.freeze([...mapped.values()]); + } + + return all; + }, + + /** @inheritdoc */ + set all(items: readonly vscode.TestItem[]) { + const newMapped = new Map(); + const toDelete = new Set(mapped.keys()); + const bulk: ITestItemBulkReplace = { op: ExtHostTestItemEventOp.Bulk, ops: [] }; + + for (const item of items) { + if (!(item instanceof TestItemImpl)) { + throw new InvalidTestItemError(item.id); + } + + if (newMapped.has(item.id)) { + throw new DuplicateTestItemError(item.id); + } + + newMapped.set(item.id, item); + toDelete.delete(item.id); + bulk.ops.push({ op: ExtHostTestItemEventOp.Upsert, item }); + } + + for (const id of toDelete.keys()) { + bulk.ops.push({ op: ExtHostTestItemEventOp.RemoveChild, id }); + } + + api.listener?.(bulk); + + // important mutations come after firing, so if an error happens no + // changes will be "saved": + mapped = newMapped; + all = undefined; + }, + + + /** @inheritdoc */ + add(item: vscode.TestItem) { + if (!(item instanceof TestItemImpl)) { + throw new InvalidTestItemError(item.id); + } + + mapped.set(item.id, item); + all = undefined; + api.listener?.({ op: ExtHostTestItemEventOp.Upsert, item }); + }, + + /** @inheritdoc */ + remove(id: string) { + if (mapped.delete(id)) { + all = undefined; + api.listener?.({ op: ExtHostTestItemEventOp.RemoveChild, id }); + } + }, + + /** @inheritdoc */ + get(itemId: string) { + return mapped.get(itemId); + }, + + /** JSON serialization function. */ + toJSON() { + return this.all; + }, + }; +}; + +export class TestItemImpl implements vscode.TestItem { + public readonly id!: string; + public readonly uri!: vscode.Uri | undefined; + public readonly children!: vscode.TestItemCollection; + public readonly parent!: TestItemImpl | undefined; + + public range!: vscode.Range | undefined; + public description!: string | undefined; + public label!: string; + public error!: string | vscode.MarkdownString; + public busy!: boolean; + public canResolveChildren!: boolean; + + /** + * Note that data is deprecated and here for back-compat only + */ + constructor(id: string, label: string, uri: vscode.Uri | undefined) { + const api = getPrivateApiFor(this); + + Object.defineProperties(this, { + id: { + value: id, + enumerable: true, + writable: false, + }, + uri: { + value: uri, + enumerable: true, + writable: false, + }, + parent: { + enumerable: false, + get() { return api.parent; }, + }, + children: { + value: createTestItemCollection(this), + enumerable: true, + writable: false, + }, + ...makePropDescriptors(api, label), + }); + } + + /** @deprecated back compat */ + public invalidateResults() { + getPrivateApiFor(this).listener?.({ op: ExtHostTestItemEventOp.Invalidated }); + } +} diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 52cf144bb2e1fd0b5981265a008d5a5dd45a0d81..5fb6b5ac777139cb0fa847bff2e7ec744be05b71 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -26,6 +26,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { getPrivateApiFor, TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { SaveReason } from 'vs/workbench/common/editor'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; @@ -721,6 +722,28 @@ export namespace CallHierarchyItem { return result; } + + export function from(item: vscode.CallHierarchyItem, sessionId?: string, itemId?: string): extHostProtocol.ICallHierarchyItemDto { + + sessionId = sessionId ?? (item)._sessionId; + itemId = itemId ?? (item)._itemId; + + if (sessionId === undefined || itemId === undefined) { + throw new Error('invalid item'); + } + + return { + _sessionId: sessionId, + _itemId: itemId, + name: item.name, + detail: item.detail, + kind: SymbolKind.from(item.kind), + uri: item.uri, + range: Range.from(item.range), + selectionRange: Range.from(item.selectionRange), + tags: item.tags?.map(SymbolTag.from) + }; + } } export namespace CallHierarchyIncomingCall { @@ -1643,9 +1666,7 @@ export namespace TestItem { label: item.label, uri: item.uri, range: Range.from(item.range) || null, - debuggable: item.debuggable ?? false, description: item.description || null, - runnable: item.runnable ?? true, error: item.error ? (MarkdownString.fromStrict(item.error) || null) : null, }; } @@ -1656,10 +1677,8 @@ export namespace TestItem { label: item.label, uri: item.uri, range: Range.from(item.range) || null, - debuggable: false, description: item.description || null, error: null, - runnable: true, }; } @@ -1669,29 +1688,26 @@ export namespace TestItem { label: item.label, uri: URI.revive(item.uri), range: Range.to(item.range || undefined), - dispose: () => undefined, invalidateResults: () => undefined, canResolveChildren: false, busy: false, - debuggable: item.debuggable, description: item.description || undefined, - runnable: item.runnable, }; } - export function to(item: ITestItem, parent?: vscode.TestItem): types.TestItemImpl { - const testItem = new types.TestItemImpl(item.extId, item.label, URI.revive(item.uri), undefined, parent); + export function to(item: ITestItem): TestItemImpl { + const testItem = new TestItemImpl(item.extId, item.label, URI.revive(item.uri)); testItem.range = Range.to(item.range || undefined); - testItem.debuggable = item.debuggable; testItem.description = item.description || undefined; - testItem.runnable = item.runnable; return testItem; } - export function toItemFromContext(context: ITestItemContext): types.TestItemImpl { - let node: types.TestItemImpl | undefined; + export function toItemFromContext(context: ITestItemContext): TestItemImpl { + let node: TestItemImpl | undefined; for (const test of context.tests) { - node = to(test.item, node); + const next = to(test.item); + getPrivateApiFor(next).parent = node; + node = next; } return node!; @@ -1717,7 +1733,7 @@ export namespace TestResults { const byInternalId = new Map(); for (const item of serialized.items) { byInternalId.set(item.item.extId, item); - if (item.direct) { + if (serialized.request.targets.some(t => t.controllerId === item.controllerId && t.testIds.includes(item.item.extId))) { roots.push(item); } } @@ -1780,3 +1796,44 @@ export namespace CodeActionTriggerKind { } } } + +export namespace TypeHierarchyItem { + + export function to(item: extHostProtocol.ITypeHierarchyItemDto): types.TypeHierarchyItem { + const result = new types.TypeHierarchyItem( + SymbolKind.to(item.kind), + item.name, + item.detail || '', + URI.revive(item.uri), + Range.to(item.range), + Range.to(item.selectionRange) + ); + + result._sessionId = item._sessionId; + result._itemId = item._itemId; + + return result; + } + + export function from(item: vscode.TypeHierarchyItem, sessionId?: string, itemId?: string): extHostProtocol.ITypeHierarchyItemDto { + + sessionId = sessionId ?? (item)._sessionId; + itemId = itemId ?? (item)._itemId; + + if (sessionId === undefined || itemId === undefined) { + throw new Error('invalid item'); + } + + return { + _sessionId: sessionId, + _itemId: itemId, + kind: SymbolKind.from(item.kind), + name: item.name, + detail: item.detail ?? '', + uri: item.uri, + range: Range.from(item.range), + selectionRange: Range.from(item.selectionRange), + tags: item.tags?.map(SymbolTag.from) + }; + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2cc35df7c9882a5addffa3a6fc24eb214bcee715..ecccb1ab07608142404b5d45c1efc8363940e3a6 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -7,14 +7,13 @@ import { asArray, coalesceInPlace, equals } from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; import { MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent'; -import { ReadonlyMapView, ResourceMap } from 'vs/base/common/map'; +import { ResourceMap } from 'vs/base/common/map'; import { Mimes, normalizeMimeType } from 'vs/base/common/mime'; import { isArray, isStringArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getPrivateApiFor, ExtHostTestItemEventType, IExtHostTestItemApi } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; @@ -1243,6 +1242,7 @@ export class CallHierarchyItem { _itemId?: string; kind: SymbolKind; + tags?: SymbolTag[]; name: string; detail?: string; uri: URI; @@ -1755,8 +1755,9 @@ export enum TaskPanelKind { } @es5ClassCompat -export class TaskGroup implements vscode.TaskGroup { +export class TaskGroup implements vscode.TaskGroup2 { + isDefault?: boolean; private _id: string; public static Clean: TaskGroup = new TaskGroup('clean', 'Clean'); @@ -3302,128 +3303,21 @@ export enum TestMessageSeverity { Hint = 3 } -const testItemPropAccessor = ( - api: IExtHostTestItemApi, - key: K, - defaultValue: vscode.TestItem[K], - equals: (a: vscode.TestItem[K], b: vscode.TestItem[K]) => boolean -) => { - let value = defaultValue; - return { - enumerable: true, - configurable: false, - get() { - return value; - }, - set(newValue: vscode.TestItem[K]) { - if (!equals(value, newValue)) { - value = newValue; - api.bus.fire([ExtHostTestItemEventType.SetProp, key, newValue]); - } - }, - }; -}; - -const strictEqualComparator = (a: T, b: T) => a === b; -const rangeComparator = (a: vscode.Range | undefined, b: vscode.Range | undefined) => { - if (a === b) { return true; } - if (!a || !b) { return false; } - return a.isEqual(b); -}; +export enum TestRunConfigurationGroup { + Run = 1, + Debug = 2, + Coverage = 3, +} +@es5ClassCompat export class TestRunRequest implements vscode.TestRunRequest { constructor( - public readonly tests: vscode.TestItem[], + public readonly include?: vscode.TestItem[], public readonly exclude?: vscode.TestItem[] | undefined, - public readonly debug = false, + public readonly configuration?: vscode.TestRunConfiguration, ) { } } -export class TestItemImpl implements vscode.TestItem { - public readonly id!: string; - public readonly uri!: vscode.Uri | undefined; - public readonly children!: ReadonlyMap; - public readonly parent!: TestItemImpl | undefined; - - public range!: vscode.Range | undefined; - public description!: string | undefined; - public runnable!: boolean; - public debuggable!: boolean; - public label!: string; - public error!: string | vscode.MarkdownString; - public busy!: boolean; - public canResolveChildren!: boolean; - - /** - * Note that data is deprecated and here for back-compat only - */ - constructor(id: string, label: string, uri: vscode.Uri | undefined, public data: any, parent: vscode.TestItem | undefined) { - const api = getPrivateApiFor(this); - - Object.defineProperties(this, { - id: { - value: id, - enumerable: true, - writable: false, - }, - uri: { - value: uri, - enumerable: true, - writable: false, - }, - parent: { - enumerable: false, - value: parent, - writable: false, - }, - children: { - value: new ReadonlyMapView(api.children), - enumerable: true, - writable: false, - }, - range: testItemPropAccessor(api, 'range', undefined, rangeComparator), - label: testItemPropAccessor(api, 'label', label, strictEqualComparator), - description: testItemPropAccessor(api, 'description', undefined, strictEqualComparator), - runnable: testItemPropAccessor(api, 'runnable', true, strictEqualComparator), - debuggable: testItemPropAccessor(api, 'debuggable', false, strictEqualComparator), - canResolveChildren: testItemPropAccessor(api, 'canResolveChildren', false, strictEqualComparator), - busy: testItemPropAccessor(api, 'busy', false, strictEqualComparator), - error: testItemPropAccessor(api, 'error', undefined, strictEqualComparator), - }); - - if (parent) { - if (!(parent instanceof TestItemImpl)) { - throw new Error(`The "parent" passed in for TestItem ${id} is invalid`); - } - - const parentApi = getPrivateApiFor(parent); - if (parentApi.children.has(id)) { - throw new Error(`Attempted to insert a duplicate test item ID ${id}`); - } - - parentApi.children.set(id, this); - parentApi.bus.fire([ExtHostTestItemEventType.NewChild, this]); - } - } - - /** @deprecated back compat */ - public invalidate() { - return this.invalidateResults(); - } - - public invalidateResults() { - getPrivateApiFor(this).bus.fire([ExtHostTestItemEventType.Invalidated]); - } - - public dispose() { - if (this.parent) { - getPrivateApiFor(this.parent).children.delete(this.id); - } - - getPrivateApiFor(this).bus.fire([ExtHostTestItemEventType.Disposed]); - } -} - @es5ClassCompat export class TestMessage implements vscode.TestMessage { public severity = TestMessageSeverity.Error; @@ -3539,3 +3433,25 @@ export enum PortAutoForwardAction { Ignore = 5, OpenBrowserOnce = 6 } + +export class TypeHierarchyItem { + _sessionId?: string; + _itemId?: string; + + kind: SymbolKind; + tags?: SymbolTag[]; + name: string; + detail?: string; + uri: URI; + range: Range; + selectionRange: Range; + + constructor(kind: SymbolKind, name: string, detail: string, uri: URI, range: Range, selectionRange: Range) { + this.kind = kind; + this.name = name; + this.detail = detail; + this.uri = uri; + this.range = range; + this.selectionRange = selectionRange; + } +} diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index 066b22f90ea7f81bffdad17433aff4122402efe3..5f651acbc80fe707cdcd1c5e1376a36908325fb3 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -82,6 +82,11 @@ export interface TaskHandleDTO { workspaceFolder: UriComponents | string; } +export interface TaskGroupDTO { + isDefault?: boolean; + _id: string; +} + export interface TaskDTO { _id: string; name?: string; @@ -89,7 +94,7 @@ export interface TaskDTO { definition: TaskDefinitionDTO; isBackground?: boolean; source: TaskSourceDTO; - group?: string; + group?: TaskGroupDTO; detail?: string; presentationOptions?: TaskPresentationOptionsDTO; problemMatchers: string[]; diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts index c6dbd5b20d5b2766c6d3f328427055009bb6c4cb..4528574457c55d51d7b0b38cd5b4db572971b126 100644 --- a/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -22,6 +22,11 @@ export class ExtensionStoragePaths extends CommonExtensionStoragePaths { return workspaceStorageURI; } + if (this._environment.skipWorkspaceStorageLock) { + this._logService.info(`Skipping acquiring lock for ${workspaceStorageURI.fsPath}.`); + return workspaceStorageURI; + } + const workspaceStorageBase = workspaceStorageURI.fsPath; let attempt = 0; do { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 985bbd0d72b99c6d72e799f098e482d271f12cf5..ba3de9d227a1cd4ee9c847fd2dcfbb67916286f3 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -607,7 +607,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const resource = EditorResourceAccessor.getOriginalUri(editor); const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined; if (resource && path) { - descriptor['resource'] = { mimeType: guessMimeTypes(resource).join(', '), scheme: resource.scheme, ext: extname(resource), path: hash(path) }; + let resourceExt = extname(resource); + // Remove query parameters from the resource extension + const queryStringLocation = resourceExt.indexOf('?'); + resourceExt = queryStringLocation !== -1 ? resourceExt.substr(0, queryStringLocation) : resourceExt; + descriptor['resource'] = { mimeType: guessMimeTypes(resource).join(', '), scheme: resource.scheme, ext: resourceExt, path: hash(path) }; /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 15f5e47dca80973f9202e9db3d52d7789f855529..241e412eb4b0bcf3dc99286961f2c43fb092aa15 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1093,7 +1093,14 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService const editors = await Promise.all(paths.map(async path => { const resource = URI.revive(path.fileUri); - if (!resource || !fileService.canHandleResource(resource)) { + + if (!resource) { + return; + } + + await fileService.activateProvider(resource.scheme); + + if (!fileService.canHandleResource(resource)) { return; } diff --git a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts index 996eddc5b184b43790154354da2b34e65128417d..314ec02cec87354c26c68b4387f87629ffd4569d 100644 --- a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, RefCountedDisposable } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -62,26 +62,6 @@ export interface CallHierarchyProvider { export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry(); -class RefCountedDisposabled { - - constructor( - private readonly _disposable: IDisposable, - private _counter = 1 - ) { } - - acquire() { - this._counter++; - return this; - } - - release() { - if (--this._counter === 0) { - this._disposable.dispose(); - } - return this; - } -} - export class CallHierarchyModel { static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise { @@ -93,7 +73,7 @@ export class CallHierarchyModel { if (!session) { return undefined; } - return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposabled(session)); + return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposable(session)); } readonly root: CallHierarchyItem; @@ -102,7 +82,7 @@ export class CallHierarchyModel { readonly id: string, readonly provider: CallHierarchyProvider, readonly roots: CallHierarchyItem[], - readonly ref: RefCountedDisposabled, + readonly ref: RefCountedDisposable, ) { this.root = roots[0]; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index df8e482142204e5e94342e9943a38d16ed7a579e..bfa07647ef1d9ed0a71c37250f6b68fdb9101cf6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -247,16 +247,25 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { } const editorOrModel = findEditor(textEditorModel, this.codeEditorService) || textEditorModel; - const mode = this.configurationService.getValue<'file' | 'modifications'>('editor.formatOnSaveMode', overrides); - if (mode === 'modifications') { - // format modifications + const mode = this.configurationService.getValue<'file' | 'modifications' | 'modificationsIfAvailable'>('editor.formatOnSaveMode', overrides); + + // keeping things DRY :) + const formatWholeFile = async () => { + await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token); + }; + + if (mode === 'modifications' || mode === 'modificationsIfAvailable') { + // try formatting modifications const ranges = await this.instantiationService.invokeFunction(getModifiedRanges, isCodeEditor(editorOrModel) ? editorOrModel.getModel() : editorOrModel); if (ranges) { + // version control reports changes await this.instantiationService.invokeFunction(formatDocumentRangesWithSelectedProvider, editorOrModel, ranges, FormattingMode.Silent, nestedProgress, token); + } else if (ranges === null) { + // version control not found + await formatWholeFile(); } } else { - // format the whole file - await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token); + await formatWholeFile(); } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 7fd09e34fee8414d5f17c54601fcfeac8f35d893..a13822a2846861dc6dc772e5a7beb58b69470b3c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -15,7 +15,6 @@ import { ITextModel } from 'vs/editor/common/model'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { DefaultSettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { Codicon } from 'vs/base/common/codicons'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -162,10 +161,6 @@ function canToggleWordWrap(editor: ICodeEditor | null): editor is IActiveCodeEdi if (!editor) { return false; } - if (editor.getContribution(DefaultSettingsEditorContribution.ID)) { - // in the settings editor... - return false; - } if (editor.isSimpleWidget) { // in a simple widget... return false; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 6e87acfd99b1b4d2e8440693c1b6eb932ad3c16c..8ce0cb8d6f379303c300602902430a8649744c22 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -294,11 +294,13 @@ configurationRegistry.registerConfiguration({ 'default': 'file', 'enum': [ 'file', - 'modifications' + 'modifications', + 'modificationsIfAvailable' ], 'enumDescriptions': [ nls.localize({ key: 'everything', comment: ['This is the description of an option'] }, "Format the whole file."), nls.localize({ key: 'modification', comment: ['This is the description of an option'] }, "Format modifications (requires source control)."), + nls.localize({ key: 'modificationIfAvailable', comment: ['This is the description of an option'] }, "Will attempt to format modifications only (requires source control). If source control can't be used, then the whole file will be formatted."), ], 'markdownDescription': nls.localize('formatOnSaveMode', "Controls if format on save formats the whole file or only modifications. Only applies when `#editor.formatOnSave#` is enabled."), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE, diff --git a/src/vs/workbench/contrib/format/browser/formatModified.ts b/src/vs/workbench/contrib/format/browser/formatModified.ts index 6fa8509b10312693bb088effd6afe5300a855685..d3ed631b0d2c59d51812ec617958ea9d6e549663 100644 --- a/src/vs/workbench/contrib/format/browser/formatModified.ts +++ b/src/vs/workbench/contrib/format/browser/formatModified.ts @@ -49,14 +49,14 @@ registerEditorAction(class FormatModifiedAction extends EditorAction { }); -export async function getModifiedRanges(accessor: ServicesAccessor, modified: ITextModel): Promise { +export async function getModifiedRanges(accessor: ServicesAccessor, modified: ITextModel): Promise { const scmService = accessor.get(ISCMService); const workerService = accessor.get(IEditorWorkerService); const modelService = accessor.get(ITextModelService); const original = await getOriginalResource(scmService, modified.uri); if (!original) { - return undefined; + return null; // let undefined signify no changes, null represents no source control (there's probably a better way, but I can't think of one rn) } const ranges: Range[] = []; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts index 0b6f11c32df560c405d6710fa5f9507fd3d688e5..e4c3cf05c72684e287f452a391f444017f7d5008 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts @@ -40,7 +40,8 @@ export class CellEditorOptions extends Disposable { glyphMargin: false, fixedOverflowWidgets: true, minimap: { enabled: false }, - renderValidationDecorations: 'on' + renderValidationDecorations: 'on', + lineNumbersMinChars: 3 }; private _value: IEditorOptions; diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 248c22d0acb72cf8c63d79ee9bf1adc245e3d0ea..561aeb6d5b1a5c6e3ff0c89f41b07dc440943a29 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -8,6 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/preferences'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest'; import * as nls from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -18,38 +19,34 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; +import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { ExplorerFolderContext, ExplorerRootContext } from 'vs/workbench/contrib/files/common/files'; import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keybindingsEditor'; import { ConfigureLanguageBasedSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; -import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; +import { SettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; +import { preferencesOpenSettingsIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { SettingsEditor2, SettingsFocusContext } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { DefaultPreferencesEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; -import { preferencesOpenSettingsIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { AbstractSideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search'; -const SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING = 'settings.action.focusNextSetting'; -const SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING = 'settings.action.focusPreviousSetting'; const SETTINGS_EDITOR_COMMAND_FOCUS_FILE = 'settings.action.focusSettingsFile'; -const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.editFocusedSetting'; const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch'; const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList'; const SETTINGS_EDITOR_COMMAND_FOCUS_TOC = 'settings.action.focusTOC'; @@ -64,17 +61,6 @@ const SETTINGS_EDITOR_COMMAND_FILTER_UNTRUSTED = 'settings.filterUntrusted'; const SETTINGS_COMMAND_OPEN_SETTINGS = 'workbench.action.openSettings'; -Registry.as(EditorExtensions.EditorPane).registerEditorPane( - EditorPaneDescriptor.create( - PreferencesEditor, - PreferencesEditor.ID, - nls.localize('defaultPreferencesEditor', "Default Preferences Editor") - ), - [ - new SyncDescriptor(PreferencesEditorInput) - ] -); - Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( SettingsEditor2, @@ -97,14 +83,6 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane ] ); -// Register Preferences Editor Input Serializer -class PreferencesEditorInputSerializer extends AbstractSideBySideEditorInputSerializer { - - protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput { - return new PreferencesEditorInput(name, description, secondaryInput, primaryInput); - } -} - class KeybindingsEditorInputSerializer implements IEditorSerializer { canSerialize(editorInput: EditorInput): boolean { @@ -135,34 +113,6 @@ class SettingsEditor2InputSerializer implements IEditorSerializer { } } -interface ISerializedDefaultPreferencesEditorInput { - resource: string; -} - -// Register Default Preferences Editor Input Serializer -class DefaultPreferencesEditorInputSerializer implements IEditorSerializer { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - const input = editorInput; - - const serialized: ISerializedDefaultPreferencesEditorInput = { resource: input.resource.toString() }; - - return JSON.stringify(serialized); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - const deserialized: ISerializedDefaultPreferencesEditorInput = JSON.parse(serializedEditorInput); - - return instantiationService.createInstance(DefaultPreferencesEditorInput, URI.parse(deserialized.resource)); - } -} - -Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(PreferencesEditorInput.ID, PreferencesEditorInputSerializer); -Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(DefaultPreferencesEditorInput.ID, DefaultPreferencesEditorInputSerializer); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(KeybindingsEditorInput.ID, KeybindingsEditorInputSerializer); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(SettingsEditor2Input.ID, SettingsEditor2InputSerializer); @@ -538,9 +488,9 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } private registerSettingsEditorActions() { - function getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null { + function getPreferencesEditor(accessor: ServicesAccessor): SettingsEditor2 | null { const activeEditorPane = accessor.get(IEditorService).activeEditorPane; - if (activeEditorPane instanceof PreferencesEditor || activeEditorPane instanceof SettingsEditor2) { + if (activeEditorPane instanceof SettingsEditor2) { return activeEditorPane; } return null; @@ -612,11 +562,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon run(accessor: ServicesAccessor, args: any): void { const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusSettingsFileEditor(); - } else if (preferencesEditor) { - preferencesEditor.focusSettings(); - } + preferencesEditor?.focusSettings(); } }); @@ -636,77 +582,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon run(accessor: ServicesAccessor, args: any): void { const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusSettingsFileEditor(); - } else if (preferencesEditor) { - preferencesEditor.focusSettings(); - } - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyCode.Enter, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.focusNextSetting', "Focus next setting") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusNextResult(); - } - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyMod.Shift | KeyCode.Enter, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.focusPreviousSetting', "Focus previous setting") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusPreviousResult(); - } - } - }); - - registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.US_DOT, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.editFocusedSetting', "Edit focused setting") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.editFocusedPreference(); - } + preferencesEditor?.focusSettings(); } }); @@ -1219,6 +1095,8 @@ const workbenchContributionsRegistry = Registry.as; - private defaultSettingsJSONEditorContextKey: IContextKey; - private searchFocusContextKey: IContextKey; - private headerContainer!: HTMLElement; - private searchWidget!: SearchWidget; - private sideBySidePreferencesWidget!: SideBySidePreferencesWidget; - private preferencesRenderers!: PreferencesRenderersController; - - private delayedFilterLogging: Delayer; - private localSearchDelayer: Delayer; - private remoteSearchThrottle: ThrottledDelayer; - private _lastReportedFilter: string | null = null; - - private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget | undefined = undefined; - - override get minimumWidth(): number { return this.sideBySidePreferencesWidget ? this.sideBySidePreferencesWidget.minimumWidth : 0; } - override get maximumWidth(): number { return this.sideBySidePreferencesWidget ? this.sideBySidePreferencesWidget.maximumWidth : Number.POSITIVE_INFINITY; } - - // these setters need to exist because this extends from EditorPane - override set minimumWidth(value: number) { /*noop*/ } - override set maximumWidth(value: number) { /*noop*/ } - - override get minimumHeight() { return 260; } - - private _onDidCreateWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); - override readonly onDidChangeSizeConstraints: Event<{ width: number; height: number; } | undefined> = this._onDidCreateWidget.event; - - constructor( - @IPreferencesService private readonly preferencesService: IPreferencesService, - @ITelemetryService telemetryService: ITelemetryService, - @IEditorService private readonly editorService: IEditorService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IEditorProgressService private readonly editorProgressService: IEditorProgressService, - @IStorageService storageService: IStorageService, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(PreferencesEditor.ID, telemetryService, themeService, storageService); - this.defaultSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(this.contextKeyService); - this.defaultSettingsJSONEditorContextKey = CONTEXT_SETTINGS_JSON_EDITOR.bindTo(this.contextKeyService); - this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(this.contextKeyService); - this.delayedFilterLogging = new Delayer(1000); - this.localSearchDelayer = new Delayer(100); - this.remoteSearchThrottle = new ThrottledDelayer(200); - } - - createEditor(parent: HTMLElement): void { - parent.classList.add('preferences-editor'); - - this.headerContainer = DOM.append(parent, DOM.$('.preferences-header')); - this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.headerContainer, { - ariaLabel: nls.localize('SearchSettingsWidget.AriaLabel', "Search settings"), - placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings"), - focusKey: this.searchFocusContextKey, - showResultCount: true, - ariaLive: 'assertive', - history: [], - })); - this._register(this.searchWidget.onDidChange(value => this.onInputChanged())); - this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget)); - this.lastFocusedWidget = this.searchWidget; - - this.createDeprecationWarning(); - - const editorsContainer = DOM.append(parent, DOM.$('.preferences-editors-container')); - this.sideBySidePreferencesWidget = this._register(this.instantiationService.createInstance(SideBySidePreferencesWidget, editorsContainer)); - this._onDidCreateWidget.fire(undefined); - this._register(this.sideBySidePreferencesWidget.onFocus(() => this.lastFocusedWidget = this.sideBySidePreferencesWidget)); - this._register(this.sideBySidePreferencesWidget.onDidSettingsTargetChange(target => this.switchSettings(target))); - - this.preferencesRenderers = this._register(this.instantiationService.createInstance(PreferencesRenderersController)); - - this._register(this.preferencesRenderers.onDidFilterResultsCountChange(count => this.showSearchResultsMessage(count))); - } - - private createDeprecationWarning(): void { - const warningIcon = DOM.$('span'); - warningIcon.className = SeverityIcon.className(Severity.Warning) + ' icon'; - const warningText = DOM.$('span', undefined, nls.localize('splitJsonRemoveWarning', "Note - this split JSON settings editor will be simplified after July.")); - const learnMore = DOM.$('span.learnMore-button.pointer', undefined, nls.localize('learnMore', "Learn More")); - const clickHandler = (e: DOM.EventLike) => { - DOM.EventHelper.stop(e, false); - this.openerService.open('https://aka.ms/AAcwpin'); - }; - - this._register(DOM.addDisposableListener(learnMore, DOM.EventType.CLICK, clickHandler)); - this._register(DOM.addDisposableListener(learnMore, DOM.EventType.KEY_DOWN, e => { - const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { - clickHandler(e); - event.preventDefault(); - event.stopPropagation(); - } - })); - const deprecationWarning = DOM.$('div.deprecation-warning', undefined, warningIcon, warningText, learnMore); - DOM.append(this.headerContainer, deprecationWarning); - } - - clearSearchResults(): void { - if (this.searchWidget) { - this.searchWidget.clear(); - } - } - - focusNextResult(): void { - if (this.preferencesRenderers) { - this.preferencesRenderers.focusNextPreference(true); - } - } - - focusPreviousResult(): void { - if (this.preferencesRenderers) { - this.preferencesRenderers.focusNextPreference(false); - } - } - - editFocusedPreference(): void { - this.preferencesRenderers.editFocusedPreference(); - } - - override setInput(input: PreferencesEditorInput, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - this.defaultSettingsEditorContextKey.set(true); - this.defaultSettingsJSONEditorContextKey.set(true); - if (options && options.query) { - this.focusSearch(options.query); - } - - return super.setInput(input, options, context, token).then(() => this.updateInput(input, options, context, token)); - } - - layout(dimension: DOM.Dimension): void { - this.searchWidget.layout(dimension); - const headerHeight = DOM.getTotalHeight(this.headerContainer); - this.sideBySidePreferencesWidget.layout(new DOM.Dimension(dimension.width, dimension.height - headerHeight)); - } - - override getControl(): IEditorControl | undefined { - return this.sideBySidePreferencesWidget.getControl(); - } - - override focus(): void { - if (this.lastFocusedWidget) { - this.lastFocusedWidget.focus(); - } - } - - focusSearch(filter?: string): void { - if (filter) { - this.searchWidget.setValue(filter); - } - - this.searchWidget.focus(); - } - - focusSettingsFileEditor(): void { - if (this.sideBySidePreferencesWidget) { - this.sideBySidePreferencesWidget.focus(); - } - } - - override clearInput(): void { - this.defaultSettingsEditorContextKey.set(false); - this.defaultSettingsJSONEditorContextKey.set(false); - this.sideBySidePreferencesWidget.clearInput(); - this.preferencesRenderers.onHidden(); - super.clearInput(); - } - - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - this.sideBySidePreferencesWidget.setEditorVisible(visible, group); - super.setEditorVisible(visible, group); - } - - private updateInput(newInput: PreferencesEditorInput, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - return this.sideBySidePreferencesWidget.setInput(newInput.secondary, newInput.primary, options, context, token).then(({ defaultPreferencesRenderer, editablePreferencesRenderer }) => { - if (token.isCancellationRequested) { - return; - } - - this.preferencesRenderers.defaultPreferencesRenderer = defaultPreferencesRenderer!; - this.preferencesRenderers.editablePreferencesRenderer = editablePreferencesRenderer!; - this.onInputChanged(); - }); - } - - private onInputChanged(): void { - const query = this.searchWidget.getValue().trim(); - this.delayedFilterLogging.cancel(); - this.triggerSearch(query) - .then(() => { - const result = this.preferencesRenderers.lastFilterResult; - if (result) { - this.delayedFilterLogging.trigger(() => this.reportFilteringUsed( - query, - this.preferencesRenderers.lastFilterResult)); - } - }); - } - - private triggerSearch(query: string): Promise { - if (query) { - return Promise.all([ - this.localSearchDelayer.trigger(() => this.preferencesRenderers.localFilterPreferences(query).then(() => { })), - this.remoteSearchThrottle.trigger(() => Promise.resolve(this.editorProgressService.showWhile(this.preferencesRenderers.remoteSearchPreferences(query), 500))) - ]).then(() => { }); - } else { - // When clearing the input, update immediately to clear it - this.localSearchDelayer.cancel(); - this.preferencesRenderers.localFilterPreferences(query); - - this.remoteSearchThrottle.cancel(); - return this.preferencesRenderers.remoteSearchPreferences(query); - } - } - - private switchSettings(target: SettingsTarget): void { - // Focus the editor if this editor is not active editor - if (this.editorService.activeEditorPane !== this) { - this.focus(); - } - const promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true); - promise.then(() => { - if (target === ConfigurationTarget.USER_LOCAL) { - this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource); - } else if (target === ConfigurationTarget.WORKSPACE) { - this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE, this.preferencesService.workspaceSettingsResource!); - } else if (target instanceof URI) { - this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE_FOLDER, target); - } - }); - } - - private showSearchResultsMessage(count: IPreferencesCount): void { - const countValue = count.count; - if (count.target) { - this.sideBySidePreferencesWidget.setResultCount(count.target, count.count); - } else if (this.searchWidget.getValue()) { - if (countValue === 0) { - this.searchWidget.showMessage(nls.localize('noSettingsFound', "No Settings Found")); - } else if (countValue === 1) { - this.searchWidget.showMessage(nls.localize('oneSettingFound', "1 Setting Found")); - } else { - this.searchWidget.showMessage(nls.localize('settingsFound', "{0} Settings Found", countValue)); - } - } else { - this.searchWidget.showMessage(nls.localize('totalSettingsMessage', "Total {0} Settings", countValue)); - } - } - - private _countById(settingsGroups: ISettingsGroup[]): IStringDictionary { - const result: IStringDictionary = {}; - - for (const group of settingsGroups) { - let i = 0; - for (const section of group.sections) { - i += section.settings.length; - } - - result[group.id] = i; - } - - return result; - } - - private reportFilteringUsed(filter: string, filterResult: IFilterResult | null): void { - if (filter && filter !== this._lastReportedFilter) { - const metadata = filterResult && filterResult.metadata; - const counts = filterResult && this._countById(filterResult.filteredGroups); - - let durations: Record | undefined; - if (metadata) { - durations = Object.create(null); - Object.keys(metadata).forEach(key => durations![key] = metadata[key].duration); - } - - const data = { - durations, - counts, - requestCount: metadata && metadata['nlpResult'] && metadata['nlpResult'].requestCount - }; - - /* __GDPR__ - "defaultSettings.filter" : { - "durations.nlpresult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "counts.nlpresult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "durations.filterresult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "counts.filterresult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "requestCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('defaultSettings.filter', data); - this._lastReportedFilter = filter; - } - } -} - -class SettingsNavigator extends ArrayNavigator { - - override next(): ISetting | null { - return super.next() || super.first(); - } - - override previous(): ISetting | null { - return super.previous() || super.last(); - } - - reset(): void { - this.index = this.start - 1; - } -} - -interface IPreferencesCount { - target?: SettingsTarget; - count: number; -} - -class PreferencesRenderersController extends Disposable { - - private _defaultPreferencesRenderer!: IPreferencesRenderer; - private _defaultPreferencesRendererDisposables: IDisposable[] = []; - - private _editablePreferencesRenderer!: IPreferencesRenderer; - private _editablePreferencesRendererDisposables: IDisposable[] = []; - - private _settingsNavigator: SettingsNavigator | null = null; - private _remoteFilterCancelToken: CancellationTokenSource | null = null; - private _prefsModelsForSearch = new Map(); - - private _currentLocalSearchProvider: ISearchProvider | null = null; - private _currentRemoteSearchProvider: ISearchProvider | null = null; - private _lastQuery = ''; - private _lastFilterResult: IFilterResult | null = null; - - private readonly _onDidFilterResultsCountChange: Emitter = this._register(new Emitter()); - readonly onDidFilterResultsCountChange: Event = this._onDidFilterResultsCountChange.event; - - constructor( - @IPreferencesSearchService private readonly preferencesSearchService: IPreferencesSearchService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IPreferencesService private readonly preferencesService: IPreferencesService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService - ) { - super(); - } - - get lastFilterResult(): IFilterResult | null { - return this._lastFilterResult; - } - - get defaultPreferencesRenderer(): IPreferencesRenderer { - return this._defaultPreferencesRenderer; - } - - get editablePreferencesRenderer(): IPreferencesRenderer { - return this._editablePreferencesRenderer; - } - - set defaultPreferencesRenderer(defaultPreferencesRenderer: IPreferencesRenderer) { - if (this._defaultPreferencesRenderer !== defaultPreferencesRenderer) { - this._defaultPreferencesRenderer = defaultPreferencesRenderer; - - this._defaultPreferencesRendererDisposables = dispose(this._defaultPreferencesRendererDisposables); - - if (this._defaultPreferencesRenderer) { - this._defaultPreferencesRenderer.onUpdatePreference(({ key, value, source }) => { - this._editablePreferencesRenderer.updatePreference(key, value, source); - this._updatePreference(key, value, source); - }, this, this._defaultPreferencesRendererDisposables); - this._defaultPreferencesRenderer.onFocusPreference(preference => this._focusPreference(preference, this._editablePreferencesRenderer), this, this._defaultPreferencesRendererDisposables); - this._defaultPreferencesRenderer.onClearFocusPreference(preference => this._clearFocus(preference, this._editablePreferencesRenderer), this, this._defaultPreferencesRendererDisposables); - } - } - } - - set editablePreferencesRenderer(editableSettingsRenderer: IPreferencesRenderer) { - if (this._editablePreferencesRenderer !== editableSettingsRenderer) { - this._editablePreferencesRenderer = editableSettingsRenderer; - this._editablePreferencesRendererDisposables = dispose(this._editablePreferencesRendererDisposables); - if (this._editablePreferencesRenderer) { - (this._editablePreferencesRenderer.preferencesModel) - .onDidChangeGroups(this._onEditableContentDidChange, this, this._editablePreferencesRendererDisposables); - - this._editablePreferencesRenderer.onUpdatePreference(({ key, value, source }) => this._updatePreference(key, value, source, true), this, this._defaultPreferencesRendererDisposables); - } - } - } - - private async _onEditableContentDidChange(): Promise { - const foundExactMatch = await this.localFilterPreferences(this._lastQuery, true); - if (!foundExactMatch) { - await this.remoteSearchPreferences(this._lastQuery, true); - } - } - - onHidden(): void { - this._prefsModelsForSearch.forEach(model => model.dispose()); - this._prefsModelsForSearch = new Map(); - } - - remoteSearchPreferences(query: string, updateCurrentResults?: boolean): Promise { - if (this.lastFilterResult && this.lastFilterResult.exactMatch) { - // Skip and clear remote search - query = ''; - } - - if (this._remoteFilterCancelToken) { - this._remoteFilterCancelToken.cancel(); - this._remoteFilterCancelToken.dispose(); - this._remoteFilterCancelToken = null; - } - - this._currentRemoteSearchProvider = (updateCurrentResults && this._currentRemoteSearchProvider) || this.preferencesSearchService.getRemoteSearchProvider(query) || null; - - this._remoteFilterCancelToken = new CancellationTokenSource(); - return this.filterOrSearchPreferences(query, this._currentRemoteSearchProvider!, 'nlpResult', nls.localize('nlpResult', "Natural Language Results"), 1, this._remoteFilterCancelToken.token, updateCurrentResults).then(() => { - if (this._remoteFilterCancelToken) { - this._remoteFilterCancelToken.dispose(); - this._remoteFilterCancelToken = null; - } - }, err => { - if (isPromiseCanceledError(err)) { - return; - } else { - onUnexpectedError(err); - } - }); - } - - localFilterPreferences(query: string, updateCurrentResults?: boolean): Promise { - if (this._settingsNavigator) { - this._settingsNavigator.reset(); - } - - this._currentLocalSearchProvider = (updateCurrentResults && this._currentLocalSearchProvider) || this.preferencesSearchService.getLocalSearchProvider(query); - return this.filterOrSearchPreferences(query, this._currentLocalSearchProvider, 'filterResult', nls.localize('filterResult', "Filtered Results"), 0, undefined, updateCurrentResults); - } - - private filterOrSearchPreferences(query: string, searchProvider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken, editableContentOnly?: boolean): Promise { - this._lastQuery = query; - - const filterPs: Promise[] = [this._filterOrSearchPreferences(query, this.editablePreferencesRenderer, searchProvider, groupId, groupLabel, groupOrder, token)]; - if (!editableContentOnly) { - filterPs.push( - this._filterOrSearchPreferences(query, this.defaultPreferencesRenderer, searchProvider, groupId, groupLabel, groupOrder, token)); - filterPs.push( - this.searchAllSettingsTargets(query, searchProvider, groupId, groupLabel, groupOrder, token).then(() => undefined)); - } - - return Promise.all(filterPs).then(results => { - let [editableFilterResult, defaultFilterResult] = results; - - if (!defaultFilterResult && editableContentOnly) { - defaultFilterResult = this.lastFilterResult!; - } - - this.consolidateAndUpdate(defaultFilterResult, editableFilterResult); - this._lastFilterResult = withUndefinedAsNull(defaultFilterResult); - - return !!(defaultFilterResult && defaultFilterResult.exactMatch); - }); - } - - private searchAllSettingsTargets(query: string, searchProvider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { - const searchPs = [ - this.searchSettingsTarget(query, searchProvider, ConfigurationTarget.WORKSPACE, groupId, groupLabel, groupOrder, token), - this.searchSettingsTarget(query, searchProvider, ConfigurationTarget.USER_LOCAL, groupId, groupLabel, groupOrder, token) - ]; - - for (const folder of this.workspaceContextService.getWorkspace().folders) { - const folderSettingsResource = this.preferencesService.getFolderSettingsResource(folder.uri); - searchPs.push(this.searchSettingsTarget(query, searchProvider, withNullAsUndefined(folderSettingsResource), groupId, groupLabel, groupOrder, token)); - } - - - return Promise.all(searchPs).then(() => { }); - } - - private searchSettingsTarget(query: string, provider: ISearchProvider, target: SettingsTarget | undefined, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { - if (!query) { - // Don't open the other settings targets when query is empty - this._onDidFilterResultsCountChange.fire({ target, count: 0 }); - return Promise.resolve(); - } - - return this.getPreferencesEditorModel(target).then(model => { - return model && this._filterOrSearchPreferencesModel('', model, provider, groupId, groupLabel, groupOrder, token); - }).then(result => { - const count = result ? this._flatten(result.filteredGroups).length : 0; - this._onDidFilterResultsCountChange.fire({ target, count }); - }, err => { - if (!isPromiseCanceledError(err)) { - return Promise.reject(err); - } - - return undefined; - }); - } - - private async getPreferencesEditorModel(target: SettingsTarget | undefined): Promise { - const resource = target === ConfigurationTarget.USER_LOCAL ? this.preferencesService.userSettingsResource : - target === ConfigurationTarget.USER_REMOTE ? this.preferencesService.userSettingsResource : - target === ConfigurationTarget.WORKSPACE ? this.preferencesService.workspaceSettingsResource : - target; - - if (!resource) { - return undefined; - } - - const targetKey = resource.toString(); - if (!this._prefsModelsForSearch.has(targetKey)) { - try { - const model = await this.preferencesService.createPreferencesEditorModel(resource); - if (model) { - this._register(model); - this._prefsModelsForSearch.set(targetKey, model); - } - } catch (e) { - // Will throw when the settings file doesn't exist. - return undefined; - } - } - - return this._prefsModelsForSearch.get(targetKey); - } - - focusNextPreference(forward: boolean = true) { - if (!this._settingsNavigator) { - return; - } - - const setting = forward ? this._settingsNavigator.next() : this._settingsNavigator.previous(); - this._focusPreference(setting, this._defaultPreferencesRenderer); - this._focusPreference(setting, this._editablePreferencesRenderer); - } - - editFocusedPreference(): void { - if (!this._settingsNavigator || !this._settingsNavigator.current()) { - return; - } - - const setting = this._settingsNavigator.current(); - const shownInEditableRenderer = this._editablePreferencesRenderer.editPreference(setting!); - if (!shownInEditableRenderer) { - this.defaultPreferencesRenderer.editPreference(setting!); - } - } - - private _filterOrSearchPreferences(filter: string, preferencesRenderer: IPreferencesRenderer, provider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { - if (!preferencesRenderer) { - return Promise.resolve(undefined); - } - - const model = preferencesRenderer.preferencesModel; - return this._filterOrSearchPreferencesModel(filter, model, provider, groupId, groupLabel, groupOrder, token).then(filterResult => { - preferencesRenderer.filterPreferences(filterResult); - return filterResult; - }); - } - - private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { - const searchP = provider ? provider.searchModel(model, token) : Promise.resolve(null); - return searchP - .then(null, err => { - if (isPromiseCanceledError(err)) { - return Promise.reject(err); - } else { - /* __GDPR__ - "defaultSettings.searchError" : { - "message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" } - } - */ - const message = getErrorMessage(err).trim(); - if (message && message !== 'Error') { - // "Error" = any generic network error - this.telemetryService.publicLogError('defaultSettings.searchError', { message }); - this.logService.info('Setting search error: ' + message); - } - return undefined; - } - }) - .then(searchResult => { - if (token && token.isCancellationRequested) { - searchResult = null; - } - - const filterResult = searchResult ? - model.updateResultGroup(groupId, { - id: groupId, - label: groupLabel, - result: searchResult, - order: groupOrder - }) : - model.updateResultGroup(groupId, undefined); - - if (filterResult) { - filterResult.query = filter; - filterResult.exactMatch = !!searchResult && searchResult.exactMatch; - } - - return filterResult; - }); - } - - private consolidateAndUpdate(defaultFilterResult: IFilterResult | undefined, editableFilterResult: IFilterResult | undefined): void { - const defaultPreferencesFilteredGroups = defaultFilterResult ? defaultFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer); - const editablePreferencesFilteredGroups = editableFilterResult ? editableFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer); - const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups); - - // Maintain the current navigation position when updating SettingsNavigator - const current = this._settingsNavigator && this._settingsNavigator.current(); - const navigatorSettings = this._lastQuery ? consolidatedSettings : []; - const currentIndex = current ? - navigatorSettings.findIndex(s => s.key === current.key) : - -1; - - this._settingsNavigator = new SettingsNavigator(navigatorSettings, Math.max(currentIndex, 0)); - - if (currentIndex >= 0) { - this._settingsNavigator.next(); - const newCurrent = this._settingsNavigator.current(); - this._focusPreference(newCurrent, this._defaultPreferencesRenderer); - this._focusPreference(newCurrent, this._editablePreferencesRenderer); - } - - const totalCount = consolidatedSettings.length; - this._onDidFilterResultsCountChange.fire({ count: totalCount }); - } - - private _getAllPreferences(preferencesRenderer: IPreferencesRenderer): ISettingsGroup[] { - return preferencesRenderer ? (preferencesRenderer.preferencesModel).settingsGroups : []; - } - - private _focusPreference(preference: ISetting | null, preferencesRenderer: IPreferencesRenderer): void { - if (preference && preferencesRenderer) { - preferencesRenderer.focusPreference(preference); - } - } - - private _clearFocus(preference: ISetting, preferencesRenderer: IPreferencesRenderer): void { - if (preference && preferencesRenderer) { - preferencesRenderer.clearFocus(preference); - } - } - - private _updatePreference(key: string, value: any, source: ISetting, fromEditableSettings?: boolean): void { - const data: { [key: string]: any; } = { - userConfigurationKeys: [key] - }; - - if (this.lastFilterResult) { - data['editableSide'] = !!fromEditableSettings; - - const nlpMetadata = this.lastFilterResult.metadata && this.lastFilterResult.metadata['nlpResult']; - if (nlpMetadata) { - const sortedKeys = Object.keys(nlpMetadata.scoredResults).sort((a, b) => nlpMetadata.scoredResults[b].score - nlpMetadata.scoredResults[a].score); - const suffix = '##' + key; - data['nlpIndex'] = sortedKeys.findIndex(key => key.endsWith(suffix)); - } - - const settingLocation = this._findSetting(this.lastFilterResult, key); - if (settingLocation) { - data['groupId'] = this.lastFilterResult.filteredGroups[settingLocation.groupIdx].id; - data['displayIdx'] = settingLocation.overallSettingIdx; - } - } - - /* __GDPR__ - "defaultSettingsActions.copySetting" : { - "userConfigurationKeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "nlpIndex" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "groupId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "displayIdx" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "editableSide" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('defaultSettingsActions.copySetting', data); - } - - private _findSetting(filterResult: IFilterResult, key: string): { groupIdx: number, settingIdx: number, overallSettingIdx: number; } | undefined { - let overallSettingIdx = 0; - - for (let groupIdx = 0; groupIdx < filterResult.filteredGroups.length; groupIdx++) { - const group = filterResult.filteredGroups[groupIdx]; - for (let settingIdx = 0; settingIdx < group.sections[0].settings.length; settingIdx++) { - const setting = group.sections[0].settings[settingIdx]; - if (key === setting.key) { - return { groupIdx, settingIdx, overallSettingIdx }; - } - - overallSettingIdx++; - } - } - - return undefined; - } - - private _consolidateSettings(editableSettingsGroups: ISettingsGroup[], defaultSettingsGroups: ISettingsGroup[]): ISetting[] { - const defaultSettings = this._flatten(defaultSettingsGroups); - const editableSettings = this._flatten(editableSettingsGroups).filter(secondarySetting => defaultSettings.every(primarySetting => primarySetting.key !== secondarySetting.key)); - return [...defaultSettings, ...editableSettings]; - } - - private _flatten(settingsGroups: ISettingsGroup[]): ISetting[] { - const settings: ISetting[] = []; - for (const group of settingsGroups) { - for (const section of group.sections) { - settings.push(...section.settings); - } - } - - return settings; - } - - override dispose(): void { - dispose(this._defaultPreferencesRendererDisposables); - dispose(this._editablePreferencesRendererDisposables); - super.dispose(); - } -} - -class SideBySidePreferencesWidget extends Widget { - - private dimension: DOM.Dimension = new DOM.Dimension(0, 0); - - private defaultPreferencesHeader: HTMLElement; - private defaultPreferencesEditor: DefaultPreferencesEditor; - private editablePreferencesEditor: EditorPane | null = null; - private defaultPreferencesEditorContainer: HTMLElement; - private editablePreferencesEditorContainer: HTMLElement; - - private settingsTargetsWidget: SettingsTargetsWidget; - - private readonly _onFocus = this._register(new Emitter()); - readonly onFocus: Event = this._onFocus.event; - - private readonly _onDidSettingsTargetChange = this._register(new Emitter()); - readonly onDidSettingsTargetChange: Event = this._onDidSettingsTargetChange.event; - - private splitview: SplitView; - - private isVisible = false; - private group: IEditorGroup | undefined; +export class SettingsEditorContribution extends Disposable { + static readonly ID: string = 'editor.contrib.settings'; - get minimumWidth(): number { return this.splitview.minimumSize; } - get maximumWidth(): number { return this.splitview.maximumSize; } + private _currentRenderer: IPreferencesRenderer | undefined; constructor( - parentElement: HTMLElement, + private readonly editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IThemeService private readonly themeService: IThemeService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IPreferencesService private readonly preferencesService: IPreferencesService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { super(); - - parentElement.classList.add('side-by-side-preferences-editor'); - - this.splitview = new SplitView(parentElement, { orientation: Orientation.HORIZONTAL }); - this._register(this.splitview); - this._register(this.splitview.onDidSashReset(() => this.splitview.distributeViewSizes())); - - this.defaultPreferencesEditorContainer = DOM.$('.default-preferences-editor-container'); - - const defaultPreferencesHeaderContainer = DOM.append(this.defaultPreferencesEditorContainer, DOM.$('.preferences-header-container')); - this.defaultPreferencesHeader = DOM.append(defaultPreferencesHeaderContainer, DOM.$('div.default-preferences-header')); - this.defaultPreferencesHeader.textContent = nls.localize('defaultSettings', "Default Settings"); - - this.defaultPreferencesEditor = this._register(this.instantiationService.createInstance(DefaultPreferencesEditor)); - this.defaultPreferencesEditor.create(this.defaultPreferencesEditorContainer); - - this.splitview.addView({ - element: this.defaultPreferencesEditorContainer, - layout: size => this.defaultPreferencesEditor.layout(new DOM.Dimension(size, this.dimension.height - 34 /* height of header container */)), - minimumSize: 220, - maximumSize: Number.POSITIVE_INFINITY, - onDidChange: Event.None - }, Sizing.Distribute); - - this.editablePreferencesEditorContainer = DOM.$('.editable-preferences-editor-container'); - const editablePreferencesHeaderContainer = DOM.append(this.editablePreferencesEditorContainer, DOM.$('.preferences-header-container')); - this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, editablePreferencesHeaderContainer, undefined)); - this._register(this.settingsTargetsWidget.onDidTargetChange(target => this._onDidSettingsTargetChange.fire(target))); - - this._register(attachStylerCallback(this.themeService, { scrollbarShadow }, colors => { - const shadow = colors.scrollbarShadow ? colors.scrollbarShadow.toString() : null; - - this.editablePreferencesEditorContainer.style.boxShadow = shadow ? `-6px 0 5px -5px ${shadow}` : ''; - })); - - this.splitview.addView({ - element: this.editablePreferencesEditorContainer, - layout: size => this.editablePreferencesEditor && this.editablePreferencesEditor.layout(new DOM.Dimension(size, this.dimension.height - 34 /* height of header container */)), - minimumSize: 220, - maximumSize: Number.POSITIVE_INFINITY, - onDidChange: Event.None - }, Sizing.Distribute); - - const focusTracker = this._register(DOM.trackFocus(parentElement)); - this._register(focusTracker.onDidFocus(() => this._onFocus.fire())); - } - - setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<{ defaultPreferencesRenderer?: IPreferencesRenderer, editablePreferencesRenderer?: IPreferencesRenderer; }> { - this.getOrCreateEditablePreferencesEditor(editablePreferencesEditorInput); - this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.resource!); - return Promise.all([ - this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, editablePreferencesEditorInput.resource!, options, context, token), - this.updateInput(this.editablePreferencesEditor!, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.resource!, options, context, token) - ]) - .then(([defaultPreferencesRenderer, editablePreferencesRenderer]) => { - if (token.isCancellationRequested) { - return {}; - } - - this.defaultPreferencesHeader.textContent = withUndefinedAsNull(defaultPreferencesRenderer && this.getDefaultPreferencesHeaderText((defaultPreferencesRenderer.preferencesModel).target)); - return { defaultPreferencesRenderer, editablePreferencesRenderer }; - }); - } - - private getDefaultPreferencesHeaderText(target: ConfigurationTarget): string { - switch (target) { - case ConfigurationTarget.USER_LOCAL: - return nls.localize('defaultUserSettings', "Default User Settings"); - case ConfigurationTarget.WORKSPACE: - return nls.localize('defaultWorkspaceSettings', "Default Workspace Settings"); - case ConfigurationTarget.WORKSPACE_FOLDER: - return nls.localize('defaultFolderSettings', "Default Folder Settings"); - } - return ''; - } - - setResultCount(settingsTarget: SettingsTarget, count: number): void { - this.settingsTargetsWidget.setResultCount(settingsTarget, count); - } - - layout(dimension: DOM.Dimension = this.dimension): void { - this.dimension = dimension; - this.splitview.layout(dimension.width); - } - - focus(): void { - if (this.editablePreferencesEditor) { - this.editablePreferencesEditor.focus(); - } - } - - getControl(): IEditorControl | undefined { - return this.editablePreferencesEditor ? this.editablePreferencesEditor.getControl() : undefined; - } - - clearInput(): void { - if (this.defaultPreferencesEditor) { - this.defaultPreferencesEditor.clearInput(); - } - if (this.editablePreferencesEditor) { - this.editablePreferencesEditor.clearInput(); - } - } - - setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - this.isVisible = visible; - this.group = group; - - if (this.defaultPreferencesEditor) { - this.defaultPreferencesEditor.setVisible(this.isVisible, this.group); - } - if (this.editablePreferencesEditor) { - this.editablePreferencesEditor.setVisible(this.isVisible, this.group); - } - } - - private getOrCreateEditablePreferencesEditor(editorInput: EditorInput): EditorPane { - if (this.editablePreferencesEditor) { - return this.editablePreferencesEditor; - } - const descriptor = Registry.as(EditorExtensions.EditorPane).getEditorPane(editorInput); - const editor = descriptor!.instantiate(this.instantiationService); - this.editablePreferencesEditor = editor; - this.editablePreferencesEditor.create(this.editablePreferencesEditorContainer); - this.editablePreferencesEditor.setVisible(this.isVisible, this.group); - this.layout(); - - return editor; + this._createPreferencesRenderer(); + this._register(this.editor.onDidChangeModel(e => this._createPreferencesRenderer())); + this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this._createPreferencesRenderer())); } - private async updateInput(editor: EditorPane, input: EditorInput, editorContributionId: string, associatedPreferencesModelUri: URI, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise | undefined> { - await editor.setInput(input, options, context, token); + private async _createPreferencesRenderer(): Promise { + this._currentRenderer?.dispose(); + this._currentRenderer = undefined; - if (token.isCancellationRequested) { - return undefined; - } - - return withNullAsUndefined( - await (editor.getControl()).getContribution(editorContributionId).updatePreferencesRenderer(associatedPreferencesModelUri)); - } - - private getSettingsTarget(resource: URI): SettingsTarget { - if (this.preferencesService.userSettingsResource.toString() === resource.toString()) { - return ConfigurationTarget.USER_LOCAL; - } - - const workspaceSettingsResource = this.preferencesService.workspaceSettingsResource; - if (workspaceSettingsResource && workspaceSettingsResource.toString() === resource.toString()) { - return ConfigurationTarget.WORKSPACE; - } - - const folder = this.workspaceContextService.getWorkspaceFolder(resource); - if (folder) { - return folder.uri; - } - - return ConfigurationTarget.USER_LOCAL; - } - - private disposeEditors(): void { - if (this.defaultPreferencesEditor) { - this.defaultPreferencesEditor.dispose(); - } - if (this.editablePreferencesEditor) { - this.editablePreferencesEditor.dispose(); - } - } - - override dispose(): void { - this.disposeEditors(); - super.dispose(); - } -} - -export class DefaultPreferencesEditor extends BaseTextEditor { - - static readonly ID: string = 'workbench.editor.defaultPreferences'; - - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IInstantiationService instantiationService: IInstantiationService, - @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, - @IThemeService themeService: IThemeService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService - ) { - super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); - } - - private static _getContributions(): IEditorContributionDescription[] { - const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID]; - const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1); - contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution as IConstructorSignature1 }); - return contributions; - } - - override createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): editorCommon.IEditor { - const editor = this.instantiationService.createInstance(CodeEditorWidget, parent, configuration, { contributions: DefaultPreferencesEditor._getContributions() }); - - // Inform user about editor being readonly if user starts type - this._register(editor.onDidType(() => this.showReadonlyHint(editor))); - this._register(editor.onDidPaste(() => this.showReadonlyHint(editor))); - - return editor; - } - - private showReadonlyHint(editor: ICodeEditor): void { - const messageController = MessageController.get(editor); - if (!messageController.isVisible()) { - messageController.showMessage(nls.localize('defaultEditorReadonly', "Edit in the right hand side editor to override defaults."), editor.getSelection()!.getPosition()); - } - } - - protected override getConfigurationOverrides(): ICodeEditorOptions { - const options = super.getConfigurationOverrides(); - options.readOnly = true; - if (this.input) { - options.lineNumbers = 'off'; - options.renderLineHighlight = 'none'; - options.scrollBeyondLastLine = false; - options.folding = false; - options.renderWhitespace = 'none'; - options.wordWrap = 'on'; - options.renderIndentGuides = false; - options.rulers = []; - options.glyphMargin = true; - options.minimap = { - enabled: false - }; - options.renderValidationDecorations = 'editable'; - } - return options; - } - - override async setInput(input: DefaultPreferencesEditorInput, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - await super.setInput(input, options, context, token); - const editorModel = await this.input!.resolve(); - if (!editorModel) { - return; - } - if (token.isCancellationRequested) { - return; - } - await editorModel.resolve(); - if (token.isCancellationRequested) { - return; - } - const editor = assertIsDefined(this.getControl()); - editor.setModel((editorModel).textEditorModel); - } - - override clearInput(): void { - // Clear Model - const editor = this.getControl(); - if (editor) { - editor.setModel(null); - } - - // Pass to super - super.clearInput(); - } - - override layout(dimension: DOM.Dimension) { - const editor = assertIsDefined(this.getControl()); - editor.layout(dimension); - } - - protected getAriaLabel(): string { - return nls.localize('preferencesAriaLabel', "Default preferences. Readonly."); - } -} - -interface ISettingsEditorContribution extends editorCommon.IEditorContribution { - - updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise | null>; - -} - -abstract class AbstractSettingsEditorContribution extends Disposable implements ISettingsEditorContribution { - - private preferencesRendererCreationPromise: Promise | null> | null = null; - - constructor(protected editor: ICodeEditor, - @IInstantiationService protected instantiationService: IInstantiationService, - @IPreferencesService protected preferencesService: IPreferencesService, - @IWorkspaceContextService protected workspaceContextService: IWorkspaceContextService - ) { - super(); - this._register(this.editor.onDidChangeModel(() => this._onModelChanged())); - } - - updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise | null> { - if (!this.preferencesRendererCreationPromise) { - this.preferencesRendererCreationPromise = this._createPreferencesRenderer(); - } - - if (this.preferencesRendererCreationPromise) { - return this._hasAssociatedPreferencesModelChanged(associatedPreferencesModelUri) - .then(changed => changed ? this._updatePreferencesRenderer(associatedPreferencesModelUri) : this.preferencesRendererCreationPromise); - } - - return Promise.resolve(null); - } - - protected _onModelChanged(): void { const model = this.editor.getModel(); - this.disposePreferencesRenderer(); if (model) { - this.preferencesRendererCreationPromise = this._createPreferencesRenderer(); - } - } - - private _hasAssociatedPreferencesModelChanged(associatedPreferencesModelUri: URI): Promise { - return this.preferencesRendererCreationPromise!.then(preferencesRenderer => { - return !(preferencesRenderer && preferencesRenderer.getAssociatedPreferencesModel() && preferencesRenderer.getAssociatedPreferencesModel().uri!.toString() === associatedPreferencesModelUri.toString()); - }); - } - - private _updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise | null> { - return this.preferencesService.createPreferencesEditorModel(associatedPreferencesModelUri) - .then(associatedPreferencesEditorModel => { - if (associatedPreferencesEditorModel) { - return this.preferencesRendererCreationPromise!.then(preferencesRenderer => { - if (preferencesRenderer) { - const associatedPreferencesModel = preferencesRenderer.getAssociatedPreferencesModel(); - if (associatedPreferencesModel) { - associatedPreferencesModel.dispose(); - } - preferencesRenderer.setAssociatedPreferencesModel(associatedPreferencesEditorModel); - } - return preferencesRenderer; - }); - } - return null; - }); - } - - private disposePreferencesRenderer(): void { - if (this.preferencesRendererCreationPromise) { - this.preferencesRendererCreationPromise.then(preferencesRenderer => { - if (preferencesRenderer) { - const associatedPreferencesModel = preferencesRenderer.getAssociatedPreferencesModel(); - if (associatedPreferencesModel) { - associatedPreferencesModel.dispose(); - } - preferencesRenderer.preferencesModel.dispose(); - preferencesRenderer.dispose(); - } - }); - this.preferencesRendererCreationPromise = Promise.resolve(null); - } - } - - override dispose() { - this.disposePreferencesRenderer(); - super.dispose(); - } - - protected abstract _createPreferencesRenderer(): Promise | null> | null; -} - -export class DefaultSettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution { - - static readonly ID: string = 'editor.contrib.defaultsettings'; - - protected _createPreferencesRenderer(): Promise | null> | null { - return this.preferencesService.createPreferencesEditorModel(this.editor.getModel()!.uri) - .then(editorModel => { - if (editorModel instanceof DefaultSettingsEditorModel && this.editor.getModel()) { - const preferencesRenderer = this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel); - preferencesRenderer.render(); - return preferencesRenderer; + const settingsModel = await this.preferencesService.createPreferencesEditorModel(model.uri); + if (settingsModel instanceof SettingsEditorModel && this.editor.getModel()) { + switch (settingsModel.configurationTarget) { + case ConfigurationTarget.WORKSPACE: + this._currentRenderer = this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel); + break; + default: + this._currentRenderer = this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel); + break; } - return null; - }); - } -} - -class SettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution { - - static readonly ID: string = 'editor.contrib.settings'; - - constructor(editor: ICodeEditor, - @IInstantiationService instantiationService: IInstantiationService, - @IPreferencesService preferencesService: IPreferencesService, - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService - ) { - super(editor, instantiationService, preferencesService, workspaceContextService); - this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this._onModelChanged())); - } + } - protected _createPreferencesRenderer(): Promise | null> | null { - const model = this.editor.getModel(); - if (model) { - return this.preferencesService.createPreferencesEditorModel(model.uri) - .then(settingsModel => { - if (settingsModel instanceof SettingsEditorModel && this.editor.getModel()) { - switch (settingsModel.configurationTarget) { - case ConfigurationTarget.USER_LOCAL: - case ConfigurationTarget.USER_REMOTE: - return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel); - case ConfigurationTarget.WORKSPACE: - return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel); - case ConfigurationTarget.WORKSPACE_FOLDER: - return this.instantiationService.createInstance(FolderSettingsRenderer, this.editor, settingsModel); - } - } - return null; - }) - .then(preferencesRenderer => { - if (preferencesRenderer) { - preferencesRenderer.render(); - } - return preferencesRenderer; - }); + this._currentRenderer?.render(); } - return null; } } - -registerEditorContribution(SettingsEditorContribution.ID, SettingsEditorContribution); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 8e0e385529847eccd311f43e867dac3808fee509..234adcce459256a201024aa791c29d142b98743b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -6,124 +6,75 @@ import { EventHelper, getDomNodePagePosition } from 'vs/base/browser/dom'; import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { 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 { IModelDeltaDecoration, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import * as modes from 'vs/editor/common/modes'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IMarkerData, IMarkerService, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { RangeHighlightDecorations } from 'vs/workbench/browser/codeeditor'; -import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; -import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; -import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { settingsEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; -import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; -import * as modes from 'vs/editor/common/modes'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { EditPreferenceWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; -import { ResourceMap } from 'vs/base/common/map'; -import { Selection } from 'vs/editor/common/core/selection'; - -export interface IPreferencesRenderer extends IDisposable { - readonly preferencesModel: IPreferencesEditorModel; - - getAssociatedPreferencesModel(): IPreferencesEditorModel; - setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel): void; - - onFocusPreference: Event; - onClearFocusPreference: Event; - onUpdatePreference: Event<{ key: string, value: any, source: T }>; +export interface IPreferencesRenderer extends IDisposable { render(): void; - updatePreference(key: string, value: any, source: T): void; - focusPreference(setting: T): void; - clearFocus(setting: T): void; - filterPreferences(filterResult: IFilterResult | undefined): void; - editPreference(setting: T): boolean; + updatePreference(key: string, value: any, source: ISetting): void; + focusPreference(setting: ISetting): void; + clearFocus(setting: ISetting): void; + editPreference(setting: ISetting): boolean; } -export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer { +export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer { private settingHighlighter: SettingHighlighter; private editSettingActionRenderer: EditSettingRenderer; - private highlightMatchesRenderer: HighlightMatchesRenderer; private modelChangeDelayer: Delayer = new Delayer(200); private associatedPreferencesModel!: IPreferencesEditorModel; - private readonly _onFocusPreference = this._register(new Emitter()); - readonly onFocusPreference: Event = this._onFocusPreference.event; - - private readonly _onClearFocusPreference = this._register(new Emitter()); - readonly onClearFocusPreference: Event = this._onClearFocusPreference.event; - - private readonly _onUpdatePreference = this._register(new Emitter<{ key: string, value: any, source: IIndexedSetting }>()); - readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; - private unsupportedSettingsRenderer: UnsupportedSettingsRenderer; - private filterResult: IFilterResult | undefined; - constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel, @IPreferencesService protected preferencesService: IPreferencesService, @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService protected instantiationService: IInstantiationService ) { super(); - this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference)); - this.highlightMatchesRenderer = this._register(instantiationService.createInstance(HighlightMatchesRenderer, editor)); + this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor)); this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter)); - this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this._updatePreference(key, value, source))); + this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this.updatePreference(key, value, source))); this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); this.unsupportedSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedSettingsRenderer, editor, preferencesModel)); } - getAssociatedPreferencesModel(): IPreferencesEditorModel { - return this.associatedPreferencesModel; - } - - setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel): void { - this.associatedPreferencesModel = associatedPreferencesModel; - this.editSettingActionRenderer.associatedPreferencesModel = associatedPreferencesModel; - - // Create header only in Settings editor mode - this.createHeader(); - } - - protected createHeader(): void { - this._register(new SettingsHeaderWidget(this.editor, '')).setMessage(nls.localize('emptyUserSettingsHeader', "Place your settings here to override the Default Settings.")); - } - render(): void { this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this.associatedPreferencesModel); - if (this.filterResult) { - this.filterPreferences(this.filterResult); - } this.unsupportedSettingsRenderer.render(); } - private _updatePreference(key: string, value: any, source: IIndexedSetting): void { - this._onUpdatePreference.fire({ key, value, source }); - this.updatePreference(key, value, source); - } - updatePreference(key: string, value: any, source: IIndexedSetting): void { const overrideIdentifier = source.overrideOf ? overrideIdentifierFromKey(source.overrideOf.key) : null; const resource = this.preferencesModel.uri; @@ -164,12 +115,6 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend return this.preferencesModel.getPreference(key); } - filterPreferences(filterResult: IFilterResult | undefined): void { - this.filterResult = filterResult; - this.settingHighlighter.clear(true); - this.highlightMatchesRenderer.render(filterResult ? filterResult.matches : []); - } - focusPreference(setting: ISetting): void { const s = this.getSetting(setting); if (s) { @@ -190,13 +135,12 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend } } -export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer { +export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer { private workspaceConfigurationRenderer: WorkspaceConfigurationRenderer; constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel, @IPreferencesService preferencesService: IPreferencesService, - @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService ) { @@ -204,431 +148,9 @@ export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements I this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel)); } - protected override createHeader(): void { - this._register(new SettingsHeaderWidget(this.editor, '')).setMessage(nls.localize('emptyWorkspaceSettingsHeader', "Place your settings here to override the User Settings.")); - } - - override setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel): void { - super.setAssociatedPreferencesModel(associatedPreferencesModel); - this.workspaceConfigurationRenderer.render(this.getAssociatedPreferencesModel()); - } - override render(): void { super.render(); - this.workspaceConfigurationRenderer.render(this.getAssociatedPreferencesModel()); - } -} - -export class FolderSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer { - - constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel, - @IPreferencesService preferencesService: IPreferencesService, - @ITelemetryService telemetryService: ITelemetryService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(editor, preferencesModel, preferencesService, configurationService, instantiationService); - } - - protected override createHeader(): void { - this._register(new SettingsHeaderWidget(this.editor, '')).setMessage(nls.localize('emptyFolderSettingsHeader', "Place your folder settings here to override those from the Workspace Settings.")); - } - -} - -export class DefaultSettingsRenderer extends Disposable implements IPreferencesRenderer { - - private _associatedPreferencesModel!: IPreferencesEditorModel; - private settingHighlighter: SettingHighlighter; - private settingsHeaderRenderer: DefaultSettingsHeaderRenderer; - private settingsGroupTitleRenderer: SettingsGroupTitleRenderer; - private filteredMatchesRenderer: FilteredMatchesRenderer; - private hiddenAreasRenderer: HiddenAreasRenderer; - private editSettingActionRenderer: EditSettingRenderer; - private bracesHidingRenderer: BracesHidingRenderer; - private filterResult: IFilterResult | undefined; - - private readonly _onUpdatePreference = this._register(new Emitter<{ key: string, value: any, source: IIndexedSetting }>()); - readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; - - private readonly _onFocusPreference = this._register(new Emitter()); - readonly onFocusPreference: Event = this._onFocusPreference.event; - - private readonly _onClearFocusPreference = this._register(new Emitter()); - readonly onClearFocusPreference: Event = this._onClearFocusPreference.event; - - constructor(protected editor: ICodeEditor, readonly preferencesModel: DefaultSettingsEditorModel, - @IPreferencesService protected preferencesService: IPreferencesService, - @IInstantiationService protected instantiationService: IInstantiationService, - ) { - super(); - this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference)); - this.settingsHeaderRenderer = this._register(instantiationService.createInstance(DefaultSettingsHeaderRenderer, editor)); - this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor)); - this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor)); - this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, preferencesModel, this.settingHighlighter)); - this.bracesHidingRenderer = this._register(instantiationService.createInstance(BracesHidingRenderer, editor)); - this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, this.bracesHidingRenderer])); - - this._register(this.editSettingActionRenderer.onUpdateSetting(e => this._onUpdatePreference.fire(e))); - this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render())); - this._register(preferencesModel.onDidChangeGroups(() => this.render())); - } - - getAssociatedPreferencesModel(): IPreferencesEditorModel { - return this._associatedPreferencesModel; - } - - setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel): void { - this._associatedPreferencesModel = associatedPreferencesModel; - this.editSettingActionRenderer.associatedPreferencesModel = associatedPreferencesModel; - } - - render() { - this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups); - this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel); - this.settingHighlighter.clear(true); - this.bracesHidingRenderer.render(undefined, this.preferencesModel.settingsGroups); - this.settingsGroupTitleRenderer.showGroup(0); - this.hiddenAreasRenderer.render(); - } - - filterPreferences(filterResult: IFilterResult | undefined): void { - this.filterResult = filterResult; - - if (filterResult) { - this.filteredMatchesRenderer.render(filterResult, this.preferencesModel.settingsGroups); - this.settingsGroupTitleRenderer.render(undefined); - this.settingsHeaderRenderer.render(filterResult); - this.settingHighlighter.clear(true); - this.bracesHidingRenderer.render(filterResult, this.preferencesModel.settingsGroups); - this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel); - } else { - this.settingHighlighter.clear(true); - this.filteredMatchesRenderer.render(undefined, this.preferencesModel.settingsGroups); - this.settingsHeaderRenderer.render(undefined); - this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups); - this.settingsGroupTitleRenderer.showGroup(0); - this.bracesHidingRenderer.render(undefined, this.preferencesModel.settingsGroups); - this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel); - } - - this.hiddenAreasRenderer.render(); - } - - focusPreference(s: ISetting): void { - const setting = this.getSetting(s); - if (setting) { - this.settingsGroupTitleRenderer.showSetting(setting); - this.settingHighlighter.highlight(setting, true); - } else { - this.settingHighlighter.clear(true); - } - } - - private getSetting(setting: ISetting): ISetting | undefined { - const { key, overrideOf } = setting; - if (overrideOf) { - const setting = this.getSetting(overrideOf); - return setting!.overrides!.find(override => override.key === key); - } - const settingsGroups = this.filterResult ? this.filterResult.filteredGroups : this.preferencesModel.settingsGroups; - return this.getPreference(key, settingsGroups); - } - - private getPreference(key: string, settingsGroups: ISettingsGroup[]): ISetting | undefined { - for (const group of settingsGroups) { - for (const section of group.sections) { - for (const setting of section.settings) { - if (setting.key === key) { - return setting; - } - } - } - } - return undefined; - } - - clearFocus(setting: ISetting): void { - this.settingHighlighter.clear(true); - } - - updatePreference(key: string, value: any, source: ISetting): void { - } - - editPreference(setting: ISetting): boolean { - return this.editSettingActionRenderer.activateOnSetting(setting); - } -} - -export interface HiddenAreasProvider { - hiddenAreas: IRange[]; -} - -export class BracesHidingRenderer extends Disposable implements HiddenAreasProvider { - private _result: IFilterResult | undefined; - private _settingsGroups!: ISettingsGroup[]; - - constructor(private editor: ICodeEditor) { - super(); - } - - render(result: IFilterResult | undefined, settingsGroups: ISettingsGroup[]): void { - this._result = result; - this._settingsGroups = settingsGroups; - } - - get hiddenAreas(): IRange[] { - // Opening square brace - const hiddenAreas = [ - { - startLineNumber: 1, - startColumn: 1, - endLineNumber: 2, - endColumn: 1 - } - ]; - - const hideBraces = (group: ISettingsGroup, hideExtraLine?: boolean) => { - // Opening curly brace - hiddenAreas.push({ - startLineNumber: group.range.startLineNumber - 3, - startColumn: 1, - endLineNumber: group.range.startLineNumber - (hideExtraLine ? 1 : 3), - endColumn: 1 - }); - - // Closing curly brace - hiddenAreas.push({ - startLineNumber: group.range.endLineNumber + 1, - startColumn: 1, - endLineNumber: group.range.endLineNumber + 4, - endColumn: 1 - }); - }; - - this._settingsGroups.forEach(g => hideBraces(g)); - if (this._result) { - this._result.filteredGroups.forEach((g, i) => hideBraces(g, true)); - } - - // Closing square brace - const lineCount = this.editor.getModel()!.getLineCount(); - hiddenAreas.push({ - startLineNumber: lineCount, - startColumn: 1, - endLineNumber: lineCount, - endColumn: 1 - }); - - - return hiddenAreas; - } - -} - -class DefaultSettingsHeaderRenderer extends Disposable { - - private settingsHeaderWidget: DefaultSettingsHeaderWidget; - readonly onClick: Event; - - constructor(editor: ICodeEditor) { - super(); - this.settingsHeaderWidget = this._register(new DefaultSettingsHeaderWidget(editor, '')); - this.onClick = this.settingsHeaderWidget.onClick; - } - - render(filterResult: IFilterResult | undefined) { - const hasSettings = !filterResult || filterResult.filteredGroups.length > 0; - this.settingsHeaderWidget.toggleMessage(hasSettings); - } -} - -export class SettingsGroupTitleRenderer extends Disposable implements HiddenAreasProvider { - - private readonly _onHiddenAreasChanged = this._register(new Emitter()); - readonly onHiddenAreasChanged: Event = this._onHiddenAreasChanged.event; - - private settingsGroups!: ISettingsGroup[]; - private hiddenGroups: ISettingsGroup[] = []; - private settingsGroupTitleWidgets!: SettingsGroupTitleWidget[]; - private readonly renderDisposables = this._register(new DisposableStore()); - - constructor(private editor: ICodeEditor, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - } - - get hiddenAreas(): IRange[] { - const hiddenAreas: IRange[] = []; - for (const group of this.hiddenGroups) { - hiddenAreas.push(group.range); - } - return hiddenAreas; - } - - render(settingsGroups: ISettingsGroup[] | undefined) { - this.disposeWidgets(); - if (!settingsGroups) { - return; - } - - this.settingsGroups = settingsGroups.slice(); - this.settingsGroupTitleWidgets = []; - for (const group of this.settingsGroups.slice().reverse()) { - if (group.sections.every(sect => sect.settings.length === 0)) { - continue; - } - - const settingsGroupTitleWidget = this.instantiationService.createInstance(SettingsGroupTitleWidget, this.editor, group); - settingsGroupTitleWidget.render(); - this.settingsGroupTitleWidgets.push(settingsGroupTitleWidget); - this.renderDisposables.add(settingsGroupTitleWidget); - this.renderDisposables.add(settingsGroupTitleWidget.onToggled(collapsed => this.onToggled(collapsed, settingsGroupTitleWidget.settingsGroup))); - } - this.settingsGroupTitleWidgets.reverse(); - } - - showGroup(groupIdx: number) { - const shownGroup = this.settingsGroupTitleWidgets[groupIdx].settingsGroup; - - this.hiddenGroups = this.settingsGroups.filter(g => g !== shownGroup); - for (const groupTitleWidget of this.settingsGroupTitleWidgets.filter(widget => widget.settingsGroup !== shownGroup)) { - groupTitleWidget.toggleCollapse(true); - } - this._onHiddenAreasChanged.fire(); - } - - showSetting(setting: ISetting): void { - const settingsGroupTitleWidget = this.settingsGroupTitleWidgets.filter(widget => Range.containsRange(widget.settingsGroup.range, setting.range))[0]; - if (settingsGroupTitleWidget && settingsGroupTitleWidget.isCollapsed()) { - settingsGroupTitleWidget.toggleCollapse(false); - this.hiddenGroups.splice(this.hiddenGroups.indexOf(settingsGroupTitleWidget.settingsGroup), 1); - this._onHiddenAreasChanged.fire(); - } - } - - private onToggled(collapsed: boolean, group: ISettingsGroup) { - const index = this.hiddenGroups.indexOf(group); - if (collapsed) { - const currentPosition = this.editor.getPosition(); - if (group.range.startLineNumber <= currentPosition!.lineNumber && group.range.endLineNumber >= currentPosition!.lineNumber) { - this.editor.setPosition({ lineNumber: group.range.startLineNumber - 1, column: 1 }); - } - this.hiddenGroups.push(group); - } else { - this.hiddenGroups.splice(index, 1); - } - this._onHiddenAreasChanged.fire(); - } - - private disposeWidgets() { - this.hiddenGroups = []; - this.renderDisposables.clear(); - } - - override dispose() { - this.disposeWidgets(); - super.dispose(); - } -} - -export class HiddenAreasRenderer extends Disposable { - - constructor(private editor: ICodeEditor, private hiddenAreasProviders: HiddenAreasProvider[] - ) { - super(); - } - - render() { - const ranges: IRange[] = []; - for (const hiddenAreaProvider of this.hiddenAreasProviders) { - ranges.push(...hiddenAreaProvider.hiddenAreas); - } - this.editor.setHiddenAreas(ranges); - } - - override dispose() { - this.editor.setHiddenAreas([]); - super.dispose(); - } -} - -export class FilteredMatchesRenderer extends Disposable implements HiddenAreasProvider { - - private decorationIds: string[] = []; - hiddenAreas: IRange[] = []; - - constructor(private editor: ICodeEditor - ) { - super(); - } - - render(result: IFilterResult | undefined, allSettingsGroups: ISettingsGroup[]): void { - this.hiddenAreas = []; - if (result) { - this.hiddenAreas = this.computeHiddenRanges(result.filteredGroups, result.allGroups); - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, result.matches.map(match => this.createDecoration(match))); - } else { - this.hiddenAreas = this.computeHiddenRanges(undefined, allSettingsGroups); - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); - } - } - - private createDecoration(range: IRange): IModelDeltaDecoration { - return { - range, - options: FindDecorations._FIND_MATCH_DECORATION - }; - } - - private computeHiddenRanges(filteredGroups: ISettingsGroup[] | undefined, allSettingsGroups: ISettingsGroup[]): IRange[] { - // Hide the contents of hidden groups - const notMatchesRanges: IRange[] = []; - if (filteredGroups) { - allSettingsGroups.forEach((group, i) => { - notMatchesRanges.push({ - startLineNumber: group.range.startLineNumber - 1, - startColumn: group.range.startColumn, - endLineNumber: group.range.endLineNumber, - endColumn: group.range.endColumn - }); - }); - } - - return notMatchesRanges; - } - - override dispose() { - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); - super.dispose(); - } -} - -export class HighlightMatchesRenderer extends Disposable { - - private decorationIds: string[] = []; - - constructor(private editor: ICodeEditor - ) { - super(); - } - - render(matches: IRange[]): void { - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, matches.map(match => this.createDecoration(match))); - } - - private createDecoration(range: IRange): IModelDeltaDecoration { - return { - range, - options: FindDecorations._FIND_MATCH_DECORATION - }; - } - - override dispose() { - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); - super.dispose(); + this.workspaceConfigurationRenderer.render(); } } @@ -910,20 +432,14 @@ class SettingHighlighter extends Disposable { private fixedHighlighter: RangeHighlightDecorations; private volatileHighlighter: RangeHighlightDecorations; - private highlightedSetting!: ISetting; - constructor(private editor: ICodeEditor, private readonly focusEventEmitter: Emitter, private readonly clearFocusEventEmitter: Emitter, - @IInstantiationService instantiationService: IInstantiationService - ) { + constructor(private editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService) { super(); this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); - this.fixedHighlighter.onHighlightRemoved(() => this.clearFocusEventEmitter.fire(this.highlightedSetting)); - this.volatileHighlighter.onHighlightRemoved(() => this.clearFocusEventEmitter.fire(this.highlightedSetting)); } highlight(setting: ISetting, fix: boolean = false) { - this.highlightedSetting = setting; this.volatileHighlighter.removeHighlightRange(); this.fixedHighlighter.removeHighlightRange(); @@ -934,7 +450,6 @@ class SettingHighlighter extends Disposable { }, this.editor); this.editor.revealLineInCenterIfOutsideViewport(setting.valueRange.startLineNumber, editorCommon.ScrollType.Smooth); - this.focusEventEmitter.fire(setting); } clear(fix: boolean = false): void { @@ -942,7 +457,6 @@ class SettingHighlighter extends Disposable { if (fix) { this.fixedHighlighter.removeHighlightRange(); } - this.clearFocusEventEmitter.fire(this.highlightedSetting); } } @@ -1149,9 +663,9 @@ class UnsupportedSettingsRenderer extends Disposable implements modes.CodeAction } class WorkspaceConfigurationRenderer extends Disposable { + private static readonly supportedKeys = ['folders', 'tasks', 'launch', 'extensions', 'settings', 'remoteAuthority', 'transient']; private decorationIds: string[] = []; - private associatedSettingsEditorModel!: IPreferencesEditorModel; private renderingDelayer: Delayer = new Delayer(200); constructor(private editor: ICodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel, @@ -1159,28 +673,17 @@ class WorkspaceConfigurationRenderer extends Disposable { @IMarkerService private readonly markerService: IMarkerService ) { super(); - this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render(this.associatedSettingsEditorModel)))); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render()))); } - render(associatedSettingsEditorModel: IPreferencesEditorModel): void { - this.associatedSettingsEditorModel = associatedSettingsEditorModel; + render(): void { const markerData: IMarkerData[] = []; if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceSettingsEditorModel instanceof WorkspaceConfigurationEditorModel) { const ranges: IRange[] = []; for (const settingsGroup of this.workspaceSettingsEditorModel.configurationGroups) { for (const section of settingsGroup.sections) { for (const setting of section.settings) { - if (setting.key === 'folders' || setting.key === 'tasks' || setting.key === 'launch' || setting.key === 'extensions') { - if (this.associatedSettingsEditorModel) { - // Dim other configurations in workspace configuration file only in the context of Settings Editor - ranges.push({ - startLineNumber: setting.keyRange.startLineNumber, - startColumn: setting.keyRange.startColumn - 1, - endLineNumber: setting.valueRange.endLineNumber, - endColumn: setting.valueRange.endColumn - }); - } - } else if (setting.key !== 'settings' && setting.key !== 'remoteAuthority' && setting.key !== 'transient') { + if (!WorkspaceConfigurationRenderer.supportedKeys.includes(setting.key)) { markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 31199fe0a88c18edb9c78038b40eee5ee77ceb82..273cb11bbe47f0f60f9510ff2cc6981e0a15b70b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action, IAction } from 'vs/base/common/actions'; @@ -13,302 +14,27 @@ import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; -import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { Position } from 'vs/editor/common/core/position'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; +import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { Schemas } from 'vs/base/common/network'; import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme'; +import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ISettingsGroup, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { isEqual } from 'vs/base/common/resources'; -import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { settingsEditIcon, settingsGroupCollapsedIcon, settingsGroupExpandedIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; -import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; - -export class SettingsHeaderWidget extends Widget implements IViewZone { - - private id!: string; - private _domNode!: HTMLElement; - - protected titleContainer!: HTMLElement; - private messageElement!: HTMLElement; - - constructor(protected editor: ICodeEditor, private title: string) { - super(); - this.create(); - this._register(this.editor.onDidChangeConfiguration(() => this.layout())); - this._register(this.editor.onDidLayoutChange(() => this.layout())); - } - - get domNode(): HTMLElement { - return this._domNode; - } - - get heightInLines(): number { - return 1; - } - - get afterLineNumber(): number { - return 0; - } - - protected create() { - this._domNode = DOM.$('.settings-header-widget'); - - this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container')); - if (this.title) { - DOM.append(this.titleContainer, DOM.$('.title')).textContent = this.title; - } - this.messageElement = DOM.append(this.titleContainer, DOM.$('.message')); - if (this.title) { - this.messageElement.style.paddingLeft = '12px'; - } - - this.editor.changeViewZones(accessor => { - this.id = accessor.addZone(this); - this.layout(); - }); - } - - setMessage(message: string): void { - this.messageElement.textContent = message; - } - - private layout(): void { - const options = this.editor.getOptions(); - const fontInfo = options.get(EditorOption.fontInfo); - this.titleContainer.style.fontSize = fontInfo.fontSize + 'px'; - if (!options.get(EditorOption.folding)) { - this.titleContainer.style.paddingLeft = '6px'; - } - } - - override dispose() { - this.editor.changeViewZones(accessor => { - accessor.removeZone(this.id); - }); - super.dispose(); - } -} - -export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget { - - private _onClick = this._register(new Emitter()); - readonly onClick: Event = this._onClick.event; - - protected override create() { - super.create(); - - this.toggleMessage(true); - } - - toggleMessage(hasSettings: boolean): void { - if (hasSettings) { - this.setMessage(localize('defaultSettings', "Place your settings in the right hand side editor to override.")); - } else { - this.setMessage(localize('noSettingsFound', "No Settings Found.")); - } - } -} - -export class SettingsGroupTitleWidget extends Widget implements IViewZone { - - private id!: string; - private _afterLineNumber!: number; - private _domNode!: HTMLElement; - - private titleContainer!: HTMLElement; - private icon!: HTMLElement; - private title!: HTMLElement; - - private _onToggled = this._register(new Emitter()); - readonly onToggled: Event = this._onToggled.event; - - private previousPosition: Position | null = null; - - constructor(private editor: ICodeEditor, public settingsGroup: ISettingsGroup) { - super(); - this.create(); - this._register(this.editor.onDidChangeConfiguration(() => this.layout())); - this._register(this.editor.onDidLayoutChange(() => this.layout())); - this._register(this.editor.onDidChangeCursorPosition((e) => this.onCursorChange(e))); - } - - get domNode(): HTMLElement { - return this._domNode; - } - - get heightInLines(): number { - return 1.5; - } - - get afterLineNumber(): number { - return this._afterLineNumber; - } - - private create() { - this._domNode = DOM.$('.settings-group-title-widget'); - - this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container')); - this.titleContainer.tabIndex = 0; - this.onclick(this.titleContainer, () => this.toggle()); - this.onkeydown(this.titleContainer, (e) => this.onKeyDown(e)); - const focusTracker = this._register(DOM.trackFocus(this.titleContainer)); - - this._register(focusTracker.onDidFocus(() => this.toggleFocus(true))); - this._register(focusTracker.onDidBlur(() => this.toggleFocus(false))); - - this.icon = DOM.append(this.titleContainer, DOM.$('')); - this.title = DOM.append(this.titleContainer, DOM.$('.title')); - this.title.textContent = this.settingsGroup.title + ` (${this.settingsGroup.sections.reduce((count, section) => count + section.settings.length, 0)})`; - - this.updateTwisty(false); - this.layout(); - } - - private getTwistyIcon(isCollapsed: boolean): ThemeIcon { - return isCollapsed ? settingsGroupCollapsedIcon : settingsGroupExpandedIcon; - } - - private updateTwisty(collapse: boolean) { - this.icon.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!collapse))); - this.icon.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(collapse))); - } - - render() { - if (!this.settingsGroup.range) { - // #61352 - return; - } - - this._afterLineNumber = this.settingsGroup.range.startLineNumber - 2; - this.editor.changeViewZones(accessor => { - this.id = accessor.addZone(this); - this.layout(); - }); - } - - toggleCollapse(collapse: boolean) { - this.titleContainer.classList.toggle('collapsed', collapse); - this.updateTwisty(collapse); - } - - toggleFocus(focus: boolean): void { - this.titleContainer.classList.toggle('focused', focus); - } - - isCollapsed(): boolean { - return this.titleContainer.classList.contains('collapsed'); - } - - private layout(): void { - const options = this.editor.getOptions(); - const fontInfo = options.get(EditorOption.fontInfo); - const layoutInfo = this.editor.getLayoutInfo(); - this._domNode.style.width = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth + 'px'; - this.titleContainer.style.lineHeight = options.get(EditorOption.lineHeight) + 3 + 'px'; - this.titleContainer.style.height = options.get(EditorOption.lineHeight) + 3 + 'px'; - this.titleContainer.style.fontSize = fontInfo.fontSize + 'px'; - this.icon.style.minWidth = `${this.getIconSize(16)}px`; - } - - private getIconSize(minSize: number): number { - const fontSize = this.editor.getOption(EditorOption.fontInfo).fontSize; - return fontSize > 8 ? Math.max(fontSize, minSize) : 12; - } - - private onKeyDown(keyboardEvent: IKeyboardEvent): void { - switch (keyboardEvent.keyCode) { - case KeyCode.Enter: - case KeyCode.Space: - this.toggle(); - break; - case KeyCode.LeftArrow: - this.collapse(true); - break; - case KeyCode.RightArrow: - this.collapse(false); - break; - case KeyCode.UpArrow: - if (this.settingsGroup.range.startLineNumber - 3 !== 1) { - this.editor.focus(); - const lineNumber = this.settingsGroup.range.startLineNumber - 2; - if (this.editor.hasModel()) { - this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) }); - } - } - break; - case KeyCode.DownArrow: - const lineNumber = this.isCollapsed() ? this.settingsGroup.range.startLineNumber : this.settingsGroup.range.startLineNumber - 1; - this.editor.focus(); - if (this.editor.hasModel()) { - this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) }); - } - break; - } - } - - private toggle() { - this.collapse(!this.isCollapsed()); - } - - private collapse(collapse: boolean) { - if (collapse !== this.isCollapsed()) { - this.titleContainer.classList.toggle('collapsed', collapse); - this.updateTwisty(collapse); - this._onToggled.fire(collapse); - } - } - - private onCursorChange(e: ICursorPositionChangedEvent): void { - if (e.source !== 'mouse' && this.focusTitle(e.position)) { - this.titleContainer.focus(); - } - } - - private focusTitle(currentPosition: Position): boolean { - const previousPosition = this.previousPosition; - this.previousPosition = currentPosition; - if (!previousPosition) { - return false; - } - if (previousPosition.lineNumber === currentPosition.lineNumber) { - return false; - } - if (!this.settingsGroup.range) { - // #60460? - return false; - } - if (currentPosition.lineNumber === this.settingsGroup.range.startLineNumber - 1 || currentPosition.lineNumber === this.settingsGroup.range.startLineNumber - 2) { - return true; - } - if (this.isCollapsed() && currentPosition.lineNumber === this.settingsGroup.range.endLineNumber) { - return true; - } - return false; - } - - override dispose() { - this.editor.changeViewZones(accessor => { - accessor.removeZone(this.id); - }); - super.dispose(); - } -} +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class FolderSettingsActionViewItem extends BaseActionViewItem { @@ -771,8 +497,7 @@ export class EditPreferenceWidget extends Disposable { private readonly _onClick = this._register(new Emitter()); readonly onClick: Event = this._onClick.event; - constructor(private editor: ICodeEditor - ) { + constructor(private editor: ICodeEditor) { super(); this._editPreferenceDecoration = []; this._register(this.editor.onMouseDown((e: IEditorMouseEvent) => { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 8f0618bab1ae8f12b1f7f02354e7640e508f312b..2e8d189068b41b8c426aa77d56afe40b0bf13fee 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1056,7 +1056,7 @@ export class SettingsEditor2 extends EditorPane { this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; const cachedState = this.restoreCachedState(); - if (cachedState && cachedState.searchQuery) { + if (cachedState && cachedState.searchQuery || !!this.searchWidget.getValue()) { await this.onSearchInputChanged(); } else { this.refreshTOCTree(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index d1a90d48f0029e4d041224e7df5a213de6a0fcb1..924bd60f08370c8382894a637418ed75c7f33d05 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -43,7 +43,7 @@ import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticip import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { inspectSetting, ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; -import { ExcludeSettingWidget, ISettingListChangeEvent, IListDataItem, ListSettingWidget, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground, ObjectSettingWidget, IObjectDataItem, IObjectEnumOption, ObjectValue, IObjectValueSuggester, IObjectKeySuggester, focusedRowBackground, focusedRowBorder, settingsHeaderForeground, rowHoverBackground, BoolObjectSettingWidget, IBoolObjectDataItem } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { ExcludeSettingWidget, ISettingListChangeEvent, IListDataItem, ListSettingWidget, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground, ObjectSettingDropdownWidget, IObjectDataItem, IObjectEnumOption, ObjectValue, IObjectValueSuggester, IObjectKeySuggester, focusedRowBackground, focusedRowBorder, settingsHeaderForeground, rowHoverBackground, ObjectSettingCheckboxWidget } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; @@ -158,6 +158,22 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData return Object.keys(data).map(key => { if (isDefined(objectProperties) && key in objectProperties) { const defaultValue = elementDefaultValue[key]; + + if (element.setting.allKeysAreBoolean) { + return { + key: { + type: 'string', + data: key + }, + value: { + type: 'boolean', + data: data[key], + description: objectProperties[key].description + }, + removable: false + } as IObjectDataItem; + } + const valueEnumOptions = getEnumOptionsFromSchema(objectProperties[key]); return { @@ -176,7 +192,6 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData } const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema; - if (schema) { const valueEnumOptions = getEnumOptionsFromSchema(schema); return { @@ -202,37 +217,6 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData }); } -function getBoolObjectDisplayValue(element: SettingsTreeSettingElement): IBoolObjectDataItem[] { - const elementDefaultValue: Record = typeof element.defaultValue === 'object' - ? element.defaultValue ?? {} - : {}; - - const elementScopeValue: Record = typeof element.scopeValue === 'object' - ? element.scopeValue ?? {} - : {}; - - const data = element.isConfigured ? - { ...elementDefaultValue, ...elementScopeValue } : - elementDefaultValue; - - const { objectProperties } = element.setting; - - return Object.keys(data).map(key => { - if (objectProperties && key in objectProperties) { - return { - key, - value: data[key], - description: objectProperties[key].description - } as IBoolObjectDataItem; - } - - return { - key, - value: data[key], - } as IBoolObjectDataItem; - }); -} - function createArraySuggester(element: SettingsTreeSettingElement): IObjectKeySuggester { return keys => { const enumOptions: IObjectEnumOption[] = []; @@ -526,11 +510,8 @@ interface ISettingExcludeItemTemplate extends ISettingItemTemplate { } interface ISettingObjectItemTemplate extends ISettingItemTemplate { - objectWidget: ObjectSettingWidget; -} - -interface ISettingBoolObjectItemTemplate extends ISettingItemTemplate { - objectWidget: BoolObjectSettingWidget; + objectDropdownWidget?: ObjectSettingDropdownWidget, + objectCheckboxWidget?: ObjectSettingCheckboxWidget; } interface ISettingNewExtensionsTemplate extends IDisposableTemplate { @@ -1141,29 +1122,30 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr } } -export class SettingObjectRenderer extends AbstractSettingRenderer implements ITreeRenderer { - templateId = SETTINGS_OBJECT_TEMPLATE_ID; - - renderTemplate(container: HTMLElement): ISettingObjectItemTemplate { - const common = this.renderCommonTemplate(null, container, 'list'); +abstract class AbstractSettingObjectRenderer extends AbstractSettingRenderer implements ITreeRenderer { - const objectWidget = this._instantiationService.createInstance(ObjectSettingWidget, common.controlElement); - objectWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); - common.toDispose.add(objectWidget); + protected renderTemplateWithWidget(common: ISettingItemTemplate, widget: ObjectSettingCheckboxWidget | ObjectSettingDropdownWidget): ISettingObjectItemTemplate { + widget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); + common.toDispose.add(widget); const template: ISettingObjectItemTemplate = { - ...common, - objectWidget: objectWidget, + ...common }; + if (widget instanceof ObjectSettingCheckboxWidget) { + template.objectCheckboxWidget = widget; + } else { + template.objectDropdownWidget = widget; + } this.addSettingElementFocusHandler(template); - common.toDispose.add(objectWidget.onDidChangeList(e => this.onDidChangeObject(template, e))); + common.toDispose.add(widget.onDidChangeList(e => this.onDidChangeObject(template, e))); return template; } - private onDidChangeObject(template: ISettingObjectItemTemplate, e: ISettingListChangeEvent): void { + protected onDidChangeObject(template: ISettingObjectItemTemplate, e: ISettingListChangeEvent): void { + const widget = (template.objectCheckboxWidget ?? template.objectDropdownWidget)!; if (template.context) { const defaultValue: Record = typeof template.context.defaultValue === 'object' ? template.context.defaultValue ?? {} @@ -1176,7 +1158,7 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT const newValue: Record = {}; const newItems: IObjectDataItem[] = []; - template.objectWidget.items.forEach((item, idx) => { + widget.items.forEach((item, idx) => { // Item was updated if (isDefined(e.item) && e.targetIndex === idx) { newValue[e.item.key.data] = e.item.value.data; @@ -1204,7 +1186,7 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT } } // New item was added - else if (template.objectWidget.isItemNew(e.originalItem) && e.item.key.data !== '') { + else if (widget.isItemNew(e.originalItem) && e.item.key.data !== '') { newValue[e.item.key.data] = e.item.value.data; newItems.push(e.item); } @@ -1222,19 +1204,33 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT type: template.context.valueType }); - template.objectWidget.setValue(newItems); + if (template.objectCheckboxWidget) { + template.objectCheckboxWidget.setValue(newItems); + } else { + template.objectDropdownWidget!.setValue(newItems); + } } } renderElement(element: ITreeNode, index: number, templateData: ISettingObjectItemTemplate): void { super.renderSettingElement(element, index, templateData); } +} + +export class SettingObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer { + override templateId = SETTINGS_OBJECT_TEMPLATE_ID; + + renderTemplate(container: HTMLElement): ISettingObjectItemTemplate { + const common = this.renderCommonTemplate(null, container, 'list'); + const widget = this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement); + return this.renderTemplateWithWidget(common, widget); + } protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: string) => void): void { const items = getObjectDisplayValue(dataElement); const { key, objectProperties, objectPatternProperties, objectAdditionalProperties } = dataElement.setting; - template.objectWidget.setValue(items, { + template.objectDropdownWidget!.setValue(items, { settingKey: key, showAddButton: objectAdditionalProperties === false ? ( @@ -1250,99 +1246,31 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT } } -export class SettingBoolObjectRenderer extends AbstractSettingRenderer implements ITreeRenderer { - templateId = SETTINGS_BOOL_OBJECT_TEMPLATE_ID; +export class SettingBoolObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer { + override templateId = SETTINGS_BOOL_OBJECT_TEMPLATE_ID; - renderTemplate(container: HTMLElement): ISettingBoolObjectItemTemplate { + renderTemplate(container: HTMLElement): ISettingObjectItemTemplate { const common = this.renderCommonTemplate(null, container, 'list'); - - const objectWidget = this._instantiationService.createInstance(BoolObjectSettingWidget, common.controlElement); - objectWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); - common.toDispose.add(objectWidget); - - const template: ISettingBoolObjectItemTemplate = { - ...common, - objectWidget - }; - - this.addSettingElementFocusHandler(template); - - common.toDispose.add(objectWidget.onDidChangeList(e => this.onDidChangeObject(template, e))); - - return template; + const widget = this._instantiationService.createInstance(ObjectSettingCheckboxWidget, common.controlElement); + return this.renderTemplateWithWidget(common, widget); } - private onDidChangeObject(template: ISettingBoolObjectItemTemplate, e: ISettingListChangeEvent): void { + override onDidChangeObject(template: ISettingObjectItemTemplate, e: ISettingListChangeEvent): void { if (template.context) { - const defaultValue: Record = template.context?.defaultValue ?? {}; - const scopeValue: Record = template.context?.scopeValue ?? {}; - const newValue: Record = {}; - const newItems: IBoolObjectDataItem[] = []; - - template.objectWidget.items.forEach((item, idx) => { - // Item was updated - if (isDefined(e.item) && e.targetIndex === idx) { - newValue[e.item.key] = e.item.value; - newItems.push(e.item); - } - // All remaining items, but skip the one that we just updated - else if (isUndefinedOrNull(e.item) || e.item.key !== item.key) { - newValue[item.key] = item.value; - newItems.push(item); - } - }); - - // Item was deleted - if (isUndefinedOrNull(e.item)) { - delete newValue[e.originalItem.key]; - - const itemToDelete = newItems.findIndex(item => item.key === e.originalItem.key); - const defaultItemValue = defaultValue[e.originalItem.key]; - - // Item does not have a default - if (isUndefinedOrNull(defaultValue[e.originalItem.key]) && itemToDelete > -1) { - newItems.splice(itemToDelete, 1); - } else if (itemToDelete > -1) { - newItems[itemToDelete].value = defaultItemValue; - } - } - // New item was added - else if (template.objectWidget.isItemNew(e.originalItem) && e.item.key !== '') { - newValue[e.item.key] = e.item.value; - newItems.push(e.item); - } - - Object.entries(newValue).forEach(([key, value]) => { - // value from the scope has changed back to the default - if (scopeValue[key] !== value && defaultValue[key] === value) { - delete newValue[key]; - } - }); - - this._onDidChangeSetting.fire({ - key: template.context.setting.key, - value: Object.keys(newValue).length === 0 ? undefined : newValue, - type: template.context.valueType - }); + super.onDidChangeObject(template, e); // Focus this setting explicitly, in case we were previously // focused on another setting and clicked a checkbox/value container // for this setting. this._onDidFocusSetting.fire(template.context); - - template.objectWidget.setValue(newItems); } } - renderElement(element: ITreeNode, index: number, templateData: ISettingBoolObjectItemTemplate): void { - super.renderSettingElement(element, index, templateData); - } - - protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingBoolObjectItemTemplate, onChange: (value: string) => void): void { - const items = getBoolObjectDisplayValue(dataElement); + protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: string) => void): void { + const items = getObjectDisplayValue(dataElement); const { key } = dataElement.setting; - template.objectWidget.setValue(items, { + template.objectCheckboxWidget!.setValue(items, { settingKey: key }); @@ -2083,7 +2011,10 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate -1 && this.setting.type.length === 2) { - if (this.setting.type.indexOf(SettingValueType.Integer) > -1) { + } else if (isArray(this.setting.type) && this.setting.type.includes(SettingValueType.Null) && this.setting.type.length === 2) { + if (this.setting.type.includes(SettingValueType.Integer)) { this.valueType = SettingValueType.NullableInteger; - } else if (this.setting.type.indexOf(SettingValueType.Number) > -1) { + } else if (this.setting.type.includes(SettingValueType.Number)) { this.valueType = SettingValueType.NullableNumber; } else { this.valueType = SettingValueType.Complex; } } else if (isObjectSetting(this.setting)) { - this.valueType = SettingValueType.Object; - } else if (this.setting.allKeysAreBoolean) { - this.valueType = SettingValueType.BooleanObject; + if (this.setting.allKeysAreBoolean) { + this.valueType = SettingValueType.BooleanObject; + } else { + this.valueType = SettingValueType.Object; + } } else { this.valueType = SettingValueType.Complex; } @@ -613,15 +615,13 @@ function isObjectSetting({ return [schema]; })); - - // This should not render boolean only objects - return flatSchemas.every(isObjectRenderableSchema) && flatSchemas.some(({ type }) => type === 'string'); + return flatSchemas.every(isObjectRenderableSchema); } function settingTypeEnumRenderable(_type: string | string[]) { const enumRenderableSettingTypes = ['string', 'boolean', 'null', 'integer', 'number']; const type = isArray(_type) ? _type : [_type]; - return type.every(type => enumRenderableSettingTypes.indexOf(type) > -1); + return type.every(type => enumRenderableSettingTypes.includes(type)); } export const enum SearchResultIdx { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 0f2a0ae6f046d37a0b5219ac80d00ec9cee2e672..c02da8985a2ac6755632026740f1412982ad2c29 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -792,6 +792,7 @@ interface IObjectEnumData { interface IObjectBoolData { type: 'boolean'; data: boolean; + description?: string; } type ObjectKey = IObjectStringData | IObjectEnumData; @@ -827,7 +828,7 @@ interface IObjectRenderEditWidgetOptions { update(keyOrValue: ObjectKey | ObjectValue): void; } -export class ObjectSettingWidget extends AbstractListSettingWidget { +export class ObjectSettingDropdownWidget extends AbstractListSettingWidget { private currentSettingKey: string = ''; private showAddButton: boolean = true; private keySuggester: IObjectKeySuggester = () => undefined; @@ -1169,16 +1170,11 @@ export class ObjectSettingWidget extends AbstractListSettingWidget { +export class ObjectSettingCheckboxWidget extends AbstractListSettingWidget { private currentSettingKey: string = ''; - override setValue(listData: IBoolObjectDataItem[], options?: IBoolObjectSetValueOptions): void { + override setValue(listData: IObjectDataItem[], options?: IBoolObjectSetValueOptions): void { if (isDefined(options) && options.settingKey !== this.currentSettingKey) { this.model.setEditKey('none'); this.model.select(null); @@ -1188,14 +1184,15 @@ export class BoolObjectSettingWidget extends AbstractListSettingWidget, idx: number, listFocused: boolean): HTMLElement { + protected override renderDataOrEditItem(item: IListViewItem, idx: number, listFocused: boolean): HTMLElement { const rowElement = this.renderEdit(item, idx); rowElement.setAttribute('role', 'listitem'); return rowElement; } - protected renderItem(item: IBoolObjectDataItem): HTMLElement { + protected renderItem(item: IObjectDataItem): HTMLElement { // return empty object, since we always render in edit mode const rowElement = $('.setting-list-row'); return rowElement; } - protected renderEdit(item: IBoolObjectDataItem, idx: number): HTMLElement { + protected renderEdit(item: IObjectDataItem, idx: number): HTMLElement { const rowElement = $('.setting-list-edit-row.setting-list-object-row.setting-item-bool'); const changedItem = { ...item }; const onValueChange = (newValue: boolean) => { - changedItem.value = newValue; + changedItem.value.data = newValue; this.handleItemChange(item, changedItem, idx); }; - const { element, widget: checkbox } = this.renderEditWidget(changedItem.value, onValueChange); + const { element, widget: checkbox } = this.renderEditWidget((changedItem.value as IObjectBoolData).data, onValueChange); rowElement.appendChild(element); const valueElement = DOM.append(rowElement, $('.setting-list-object-value')); - valueElement.textContent = changedItem.key; - valueElement.setAttribute('title', changedItem.description ?? ''); + valueElement.textContent = changedItem.key.data; + valueElement.setAttribute('title', (changedItem.value as IObjectBoolData).description ?? ''); this._register(DOM.addDisposableListener(valueElement, DOM.EventType.MOUSE_DOWN, e => { const targetElement = e.target; if (targetElement.tagName.toLowerCase() !== 'a') { @@ -1283,8 +1280,8 @@ export class BoolObjectSettingWidget extends AbstractListSettingWidget(JSONContributionRegistry.Extensions.JSONContribution); @@ -43,7 +43,7 @@ export class PreferencesContribution implements IWorkbenchContribution { @IEditorService private readonly editorService: IEditorService, ) { this.settingsListener = this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(USE_SPLIT_JSON_SETTING)) { + if (e.affectsConfiguration(USE_SPLIT_JSON_SETTING) || e.affectsConfiguration(DEFAULT_SETTINGS_EDITOR_SETTING)) { this.handleSettingsEditorRegistration(); } }); @@ -58,20 +58,19 @@ export class PreferencesContribution implements IWorkbenchContribution { dispose(this.editorOpeningListener); // install editor opening listener unless user has disabled this - if (!!this.configurationService.getValue(USE_SPLIT_JSON_SETTING)) { + if (!!this.configurationService.getValue(USE_SPLIT_JSON_SETTING) || !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING)) { this.editorOpeningListener = this.editorResolverService.registerEditor( '**/settings.json', { - id: PreferencesEditorInput.ID, - detail: 'Split Settings Editor (deprecated)', - label: 'label', + id: SideBySideEditorInput.ID, + label: nls.localize('splitSettingsEditorLabel', "Split Settings Editor"), priority: RegisteredEditorPriority.builtin, }, {}, - ({ resource, options }, group): IEditorInputWithOptions => { + ({ resource, options }): IEditorInputWithOptions => { // Global User Settings File if (isEqual(resource, this.environmentService.settingsResource)) { - return { editor: this.preferencesService.getCurrentOrNewSplitJsonEditorInput(ConfigurationTarget.USER_LOCAL, resource, group), options }; + return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.USER_LOCAL, resource), options }; } // Single Folder Workspace Settings File @@ -79,7 +78,7 @@ export class PreferencesContribution implements IWorkbenchContribution { if (state === WorkbenchState.FOLDER) { const folders = this.workspaceService.getWorkspace().folders; if (isEqual(resource, folders[0].toResource(FOLDER_SETTINGS_PATH))) { - return { editor: this.preferencesService.getCurrentOrNewSplitJsonEditorInput(ConfigurationTarget.WORKSPACE, resource, group), options }; + return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.WORKSPACE, resource), options }; } } @@ -88,7 +87,7 @@ export class PreferencesContribution implements IWorkbenchContribution { const folders = this.workspaceService.getWorkspace().folders; for (const folder of folders) { if (isEqual(resource, folder.toResource(FOLDER_SETTINGS_PATH))) { - return { editor: this.preferencesService.getCurrentOrNewSplitJsonEditorInput(ConfigurationTarget.WORKSPACE_FOLDER, resource, group), options }; + return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.WORKSPACE_FOLDER, resource), options }; } } } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 5aa40c75ea2f13faaca8ff3050a660ffb9607134..b0bc626e7d813902a0cf08136a261e9ca89c6e5e 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -1100,12 +1100,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return Promise.resolve(task); } - private getTasksForGroup(group: string): Promise { + private getTasksForGroup(group: TaskGroup): Promise { return this.getGroupedTasks().then((groups) => { let result: Task[] = []; groups.forEach((tasks) => { for (let task of tasks) { - if (task.configurationProperties.group === group) { + if (task.configurationProperties.group?._id === group._id) { result.push(task); } } diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 7ef30c474d9e42098dca8919cd623a5443da31e9..8636375b47836154811e66e1f6535d6544b726fd 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1230,7 +1230,7 @@ const partialSource: Partial = { }; namespace GroupKind { - export function from(this: void, external: string | GroupKind | undefined): [string, Tasks.GroupType] | undefined { + export function from(this: void, external: string | GroupKind | undefined): [Tasks.TaskGroup, Tasks.GroupType] | undefined { if (external === undefined) { return undefined; } @@ -1247,7 +1247,7 @@ namespace GroupKind { let group: string = external.kind; let isDefault: boolean = !!external.isDefault; - return [group, isDefault ? Tasks.GroupType.default : Tasks.GroupType.user]; + return [{ _id: group, isDefault }, isDefault ? Tasks.GroupType.default : Tasks.GroupType.user]; } } @@ -1326,7 +1326,7 @@ namespace ConfigurationProperties { result.promptOnClose = !!external.promptOnClose; } if (external.group !== undefined) { - if (Types.isString(external.group) && Tasks.TaskGroup.is(external.group)) { + if ((external.group as Tasks.TaskGroup) && Tasks.TaskGroup.is(external.group)) { result.group = external.group; result.groupType = Tasks.GroupType.user; } else { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 9bbb50cfdfa15bd06ff289b349442e0fd8a8e3b6..9c4af5e78fc2a883e90655a213aa1ecd6f5e665e 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -365,21 +365,23 @@ export interface CommandConfiguration { } export namespace TaskGroup { - export const Clean: 'clean' = 'clean'; + export const Clean: TaskGroup = { _id: 'clean', isDefault: false }; - export const Build: 'build' = 'build'; + export const Build: TaskGroup = { _id: 'build', isDefault: false }; - export const Rebuild: 'rebuild' = 'rebuild'; + export const Rebuild: TaskGroup = { _id: 'rebuild', isDefault: false }; - export const Test: 'test' = 'test'; + export const Test: TaskGroup = { _id: 'test', isDefault: false }; - export function is(value: string): value is string { - return value === Clean || value === Build || value === Rebuild || value === Test; + export function is(value: any): value is TaskGroup { + return value === Clean._id || value === Build._id || value === Rebuild._id || value === Test._id; } } -export type TaskGroup = 'clean' | 'build' | 'rebuild' | 'test'; - +export interface TaskGroup { + _id: string; + isDefault?: boolean; +} export const enum TaskScope { Global = 1, @@ -489,9 +491,9 @@ export interface ConfigurationProperties { identifier?: string; /** - * the task's group; + * The task's group; */ - group?: string; + group?: TaskGroup; /** * The group type @@ -1076,7 +1078,7 @@ export interface TaskEvent { taskId?: string; taskName?: string; runType?: TaskRunType; - group?: string; + group?: TaskGroup; processId?: number; exitCode?: number; terminalId?: number; diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index 9b1a369523a6badd59eac7ff9d9f924cef6bbb1d..fa3320d504ff0518dc76f7b33c02564ad1b51538 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -454,7 +454,7 @@ function assertConfiguration(result: ParseResult, expected: Tasks.Task[]): void actualTasks[task.configurationProperties.name!] = task; actualId2Name[task._id] = task.configurationProperties.name!; if (task.configurationProperties.group) { - actualTaskGroups.add(task.configurationProperties.group, task); + actualTaskGroups.add(task.configurationProperties.group._id, task); } }); let expectedTasks: { [key: string]: Tasks.Task; } = Object.create(null); @@ -463,7 +463,7 @@ function assertConfiguration(result: ParseResult, expected: Tasks.Task[]): void assert.ok(!expectedTasks[task.configurationProperties.name!]); expectedTasks[task.configurationProperties.name!] = task; if (task.configurationProperties.group) { - expectedTaskGroup.add(task.configurationProperties.group, task); + expectedTaskGroup.add(task.configurationProperties.group._id, task); } }); let actualKeys = Object.keys(actualTasks); diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index f1766e56eca274ab72d608f3bb52963b7420c454..215323fd36cb4b6336a572f0eb201d9bfeac2ef3 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -194,7 +194,10 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } private getTelemetryData(resource: URI, reason?: number): TelemetryData { - const ext = extname(resource); + let ext = extname(resource); + // Remove query parameters from the resource extension + const queryStringLocation = ext.indexOf('?'); + ext = queryStringLocation !== -1 ? ext.substr(0, queryStringLocation) : ext; const fileName = basename(resource); const path = resource.scheme === Schemas.file ? resource.fsPath : resource.path; const telemetryData = { diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 88c6f74aee167cf4210ee919ae3e83c8cf4f4d53..96727f5c892c48b17efaef3b7ca572b79937d1d5 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Barrier } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; @@ -15,20 +15,22 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA export class RemotePty extends Disposable implements ITerminalChildProcess { - readonly _onProcessData = this._register(new Emitter()); - readonly onProcessData: Event = this._onProcessData.event; + private readonly _onProcessData = this._register(new Emitter()); + readonly onProcessData = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter()); - readonly onProcessExit: Event = this._onProcessExit.event; - readonly _onProcessReady = this._register(new Emitter()); - get onProcessReady(): Event { return this._onProcessReady.event; } + readonly onProcessExit = this._onProcessExit.event; + private readonly _onProcessReady = this._register(new Emitter()); + readonly onProcessReady = this._onProcessReady.event; private readonly _onProcessTitleChanged = this._register(new Emitter()); - readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; + readonly onProcessTitleChanged = this._onProcessTitleChanged.event; private readonly _onProcessShellTypeChanged = this._register(new Emitter()); readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; private readonly _onProcessOverrideDimensions = this._register(new Emitter()); - readonly onProcessOverrideDimensions: Event = this._onProcessOverrideDimensions.event; + readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); - get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessResolvedShellLaunchConfig.event; } + readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; + private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); + readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; private _startBarrier: Barrier; @@ -148,6 +150,9 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { } this._onProcessResolvedShellLaunchConfig.fire(e); } + handleDidChangeHasChildProcesses(e: boolean) { + this._onDidChangeHasChildProcesses.fire(e); + } async handleReplay(e: IPtyHostProcessReplayEvent) { try { diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index 5f31f3a6803252b6fa57c3544618acf2d9c5f836..d0e4e8e05dcc8edd7f14ec888aefcf83ab87436c 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -42,6 +42,8 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal readonly onPtyHostRestart = this._onPtyHostRestart.event; private readonly _onPtyHostRequestResolveVariables = this._register(new Emitter()); readonly onPtyHostRequestResolveVariables = this._onPtyHostRequestResolveVariables.event; + private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number, workspaceId: string, instanceId: number }>()); + readonly onDidRequestDetach = this._onDidRequestDetach.event; constructor( @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @@ -76,6 +78,8 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal channel.onProcessResolvedShellLaunchConfig(e => this._ptys.get(e.id)?.handleResolvedShellLaunchConfig(e.event)); channel.onProcessReplay(e => this._ptys.get(e.id)?.handleReplay(e.event)); channel.onProcessOrphanQuestion(e => this._ptys.get(e.id)?.handleOrphanQuestion()); + channel.onDidRequestDetach(e => this._onDidRequestDetach.fire(e)); + channel.onProcessDidChangeHasChildProcesses(e => this._ptys.get(e.id)?.handleDidChangeHasChildProcesses(e.event)); const allowedCommands = ['_remoteCLI.openExternal', '_remoteCLI.windowOpen', '_remoteCLI.getSystemStatus', '_remoteCLI.manageExtensions']; channel.onExecuteCommand(async e => { @@ -151,6 +155,21 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal } } + async requestDetachInstance(workspaceId: string, instanceId: number): Promise { + if (!this._remoteTerminalChannel) { + throw new Error(`Cannot request detach instance when there is no remote!`); + } + return this._remoteTerminalChannel.requestDetachInstance(workspaceId, instanceId); + } + + async acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise { + if (!this._remoteTerminalChannel) { + throw new Error(`Cannot accept detached instance when there is no remote!`); + } + return this._remoteTerminalChannel.acceptDetachedInstance(requestId, persistentProcessId); + } + + async createProcess(shellLaunchConfig: IShellLaunchConfig, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, shouldPersist: boolean, configHelper: ITerminalConfigHelper): Promise { if (!this._remoteTerminalChannel) { throw new Error(`Cannot create remote terminal when there is no remote!`); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 899c8c73a59f72f89dcba03ee9d25b015676d474..c1c4c71f919f3aa7081525439b72d59221f20094 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -146,6 +146,7 @@ export interface ITerminalService extends ITerminalInstanceHost { splitInstance(instance: ITerminalInstance, profile: ITerminalProfile): ITerminalInstance | null; moveToEditor(source: ITerminalInstance): void; moveToTerminalView(source?: ITerminalInstance | URI): Promise; + getOffProcessTerminalService(): IOffProcessTerminalService | undefined; /** * Perform an action with the active terminal instance, if the terminal does @@ -338,7 +339,7 @@ export interface ITerminalInstance { /** * A unique URI for this terminal instance with the following encoding: * path: Title - * fragment: Instance ID + * fragment: workspace ID / instance ID */ readonly resource: URI; @@ -400,14 +401,15 @@ export interface ITerminalInstance { */ onDisposed: Event; - onFocused: Event; onProcessIdReady: Event; onLinksReady: Event; onRequestExtHostProcess: Event; onDimensionsChanged: Event; onMaximumDimensionsChanged: Event; + onDidChangeHasChildProcesses: Event; - onFocus: Event; + onDidFocus: Event; + onDidBlur: Event; /** * An event that fires when a terminal is dropped on this instance via drag and drop. @@ -457,7 +459,10 @@ export interface ITerminalInstance { readonly initialDataEvents: string[] | undefined; /** A promise that resolves when the terminal's pty/process have been created. */ - processReady: Promise; + readonly processReady: Promise; + + /** Whether the terminal's process has child processes (ie. is dirty/busy). */ + readonly hasChildProcesses: boolean; /** * The title of the terminal. This is either title or the process currently running or an diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index e03b17d1586036131fe67ce95a75724dac1d2444..5f5717208a0debc81d52a42cc99e770fdba03751 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -34,7 +34,7 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; import { Direction, IRemoteTerminalService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; -import { ILocalTerminalService, IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TerminalCommandId, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ILocalTerminalService, IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TerminalCommandId, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -296,7 +296,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.FocusPreviousPane, - title: { value: localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Pane"), original: 'Focus Previous Pane' }, + title: { value: localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Terminal in Terminal Group"), original: 'Focus Previous Terminal in Terminal Group' }, f1: true, category, keybinding: { @@ -322,7 +322,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.FocusNextPane, - title: { value: localize('workbench.action.terminal.focusNextPane', "Focus Next Pane"), original: 'Focus Next Pane' }, + title: { value: localize('workbench.action.terminal.focusNextPane', "Focus Next Terminal in Terminal Group"), original: 'Focus Next Terminal in Terminal Group' }, f1: true, category, keybinding: { @@ -348,7 +348,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.ResizePaneLeft, - title: { value: localize('workbench.action.terminal.resizePaneLeft', "Resize Pane Left"), original: 'Resize Pane Left' }, + title: { value: localize('workbench.action.terminal.resizePaneLeft', "Resize Terminal Left"), original: 'Resize Terminal Left' }, f1: true, category, keybinding: { @@ -368,7 +368,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.ResizePaneRight, - title: { value: localize('workbench.action.terminal.resizePaneRight', "Resize Pane Right"), original: 'Resize Pane Right' }, + title: { value: localize('workbench.action.terminal.resizePaneRight', "Resize Terminal Right"), original: 'Resize Terminal Right' }, f1: true, category, keybinding: { @@ -388,7 +388,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.ResizePaneUp, - title: { value: localize('workbench.action.terminal.resizePaneUp', "Resize Pane Up"), original: 'Resize Pane Up' }, + title: { value: localize('workbench.action.terminal.resizePaneUp', "Resize Terminal Up"), original: 'Resize Terminal Up' }, f1: true, category, keybinding: { @@ -407,7 +407,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.ResizePaneDown, - title: { value: localize('workbench.action.terminal.resizePaneDown', "Resize Pane Down"), original: 'Resize Pane Down' }, + title: { value: localize('workbench.action.terminal.resizePaneDown', "Resize Terminal Down"), original: 'Resize Terminal Down' }, f1: true, category, keybinding: { @@ -465,7 +465,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.FocusNext, - title: { value: localize('workbench.action.terminal.focusNext', "Focus Next Terminal"), original: 'Focus Next Terminal' }, + title: { value: localize('workbench.action.terminal.focusNext', "Focus Next Terminal Group"), original: 'Focus Next Terminal Group' }, f1: true, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, @@ -474,7 +474,7 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET }, - when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS.negate()), weight: KeybindingWeight.WorkbenchContrib } }); @@ -489,7 +489,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.FocusPrevious, - title: { value: localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal"), original: 'Focus Previous Terminal' }, + title: { value: localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal Group"), original: 'Focus Previous Terminal Group' }, f1: true, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, @@ -498,7 +498,7 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET }, - when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS.negate()), weight: KeybindingWeight.WorkbenchContrib } }); @@ -1701,11 +1701,12 @@ export function registerTerminalActions() { } async run(accessor: ServicesAccessor) { const terminalGroupService = accessor.get(ITerminalGroupService); + const terminalService = accessor.get(ITerminalService); const instance = terminalGroupService.activeInstance; if (!instance) { return; } - instance.dispose(true); + await terminalService.safeDisposeTerminal(instance); if (terminalGroupService.instances.length > 0) { await terminalGroupService.showPanel(true); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index aa8a05c9a22141f6a2599e11854bcdc41405388d..6362b3a05612e321b076f9d930a837d363da7720 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -94,7 +94,7 @@ export class TerminalEditor extends EditorPane { // since the editor does not monitor focus changes, for ex. between the terminal // panel and the editors, this is needed so that the active instance gets set // when focus changes between them. - this._register(this._editorInput.terminalInstance.onFocused(() => this._setActiveInstance())); + this._register(this._editorInput.terminalInstance.onDidFocus(() => this._setActiveInstance())); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index e8c0020c328e397c8ecad713cfd1f172a1ebd8c7..9ffe4547dd9bd761cf377e8c47026f829a14af89 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -12,9 +12,12 @@ import { ITerminalInstance, ITerminalInstanceService } from 'vs/workbench/contri import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; +import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ConfirmOnKill, KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalEditorInput extends EditorInput { @@ -23,6 +26,7 @@ export class TerminalEditorInput extends EditorInput { private _isDetached = false; private _isShuttingDown = false; private _copyInstance?: ITerminalInstance; + private _terminalEditorFocusContextKey: IContextKey; private _group: IEditorGroup | undefined; @@ -68,15 +72,27 @@ export class TerminalEditorInput extends EditorInput { return this._terminalInstance.resource; } + override isDirty(): boolean { + const confirmOnKill = this._configurationService.getValue(TerminalSettingId.ConfirmOnKill); + if (confirmOnKill === 'editor' || confirmOnKill === 'always') { + return this._terminalInstance.hasChildProcesses; + } + return false; + } + constructor( private readonly _terminalInstance: ITerminalInstance, @IThemeService private readonly _themeService: IThemeService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ILifecycleService lifecycleService: ILifecycleService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILifecycleService lifecycleService: ILifecycleService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); + this._terminalEditorFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS.bindTo(contextKeyService); + this._register(toDisposable(() => { if (!this._isDetached && !this._isShuttingDown) { this._terminalInstance.dispose(); @@ -88,9 +104,19 @@ export class TerminalEditorInput extends EditorInput { this._terminalInstance.onDisposed(() => this.dispose()), this._terminalInstance.onTitleChanged(() => this._onDidChangeLabel.fire()), this._terminalInstance.onIconChanged(() => this._onDidChangeLabel.fire()), + this._terminalInstance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)), + this._terminalInstance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()), + this._terminalInstance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()), this._terminalInstance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire()) ]; + // Refresh dirty state when the confirm on kill setting is changed + this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) { + this._onDidChangeDirty.fire(); + } + }); + // Don't dispose editor when instance is torn down on shutdown to avoid extra work and so // the editor/tabs don't disappear lifecycleService.onWillShutdown(() => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 3b3defbc21eb99d7e26b45882bbd6c6d638ea1b7..3966544da3f810eeb0727372e8d2e603acb96295 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -172,7 +172,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor this._editorInputs.set(instance.instanceId, input); this._instanceDisposables.set(instance.instanceId, [ instance.onDisposed(this._onDidDisposeInstance.fire, this._onDidDisposeInstance), - instance.onFocus(this._onDidFocusInstance.fire, this._onDidFocusInstance) + instance.onDidFocus(this._onDidFocusInstance.fire, this._onDidFocusInstance) ]); this.instances.push(instance); this._onDidChangeInstances.fire(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 829a57d4ff7742240a133886664f1a7fa39c342b..17f4a93522e15a4d168c86bdf8fbc658a349ccc9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -337,7 +337,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this._onDidDisposeInstance.fire(instance); this._handleOnDidDisposeInstance(instance); }), - instance.onFocused(instance => { + instance.onDidFocus(instance => { this._setActiveInstance(instance); this._onDidFocusInstance.fire(instance); }) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9500b993e441a41410bd1115d351ab5f1b1bbc94..aec06225aceb1511a1f490ee4b79a0547f5bf746 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -63,6 +63,7 @@ import { getColorClass } from 'vs/workbench/contrib/terminal/browser/terminalIco import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -79,7 +80,10 @@ const enum Constants { * terminal process. This period helps ensure the terminal has good initial dimensions to work * with if it's going to be a foreground terminal. */ - WaitForContainerThreshold = 100 + WaitForContainerThreshold = 100, + + DefaultCols = 80, + DefaultRows = 30, } let xtermConstructor: Promise | undefined; @@ -156,7 +160,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get resource(): URI { return URI.from({ scheme: Schemas.vscodeTerminal, - path: `/${this.instanceId}`, + path: `/${this._workspaceContextService.getWorkspace().id}/${this.instanceId}`, fragment: this.title, }); } @@ -185,6 +189,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // TODO: How does this work with detached processes? // TODO: Should this be an event as it can fire twice? get processReady(): Promise { return this._processManager.ptyProcessReady; } + get hasChildProcesses(): boolean { return this._processManager.hasChildProcesses; } get areLinksReady(): boolean { return this._areLinksReady; } get initialDataEvents(): string[] | undefined { return this._initialDataEvents; } get exitCode(): number | undefined { return this._exitCode; } @@ -210,8 +215,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _onDisposed = this._register(new Emitter()); readonly onDisposed = this._onDisposed.event; - private readonly _onFocused = this._register(new Emitter()); - readonly onFocused = this._onFocused.event; private readonly _onProcessIdReady = this._register(new Emitter()); readonly onProcessIdReady = this._onProcessIdReady.event; private readonly _onLinksReady = this._register(new Emitter()); @@ -232,10 +235,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { readonly onDimensionsChanged = this._onDimensionsChanged.event; private readonly _onMaximumDimensionsChanged = this._register(new Emitter()); readonly onMaximumDimensionsChanged = this._onMaximumDimensionsChanged.event; - private readonly _onFocus = this._register(new Emitter()); - readonly onFocus = this._onFocus.event; + private readonly _onDidFocus = this._register(new Emitter()); + readonly onDidFocus = this._onDidFocus.event; + private readonly _onDidBlur = this._register(new Emitter()); + readonly onDidBlur = this._onDidBlur.event; private readonly _onRequestAddInstanceToGroup = this._register(new Emitter()); readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event; + private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); + readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; constructor( private readonly _terminalFocusContextKey: IContextKey, @@ -260,7 +267,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { super(); @@ -555,9 +563,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const editorOptions = this._configurationService.getValue('editor'); const xterm = new Terminal({ - // TODO: Replace null with undefined when https://github.com/xtermjs/xterm.js/issues/3329 is resolved - cols: this._cols || null as any, - rows: this._rows || null as any, + cols: this._cols || Constants.DefaultCols, + rows: this._rows || Constants.DefaultRows, altClickMovesCursor: config.altClickMovesCursor && editorOptions.multiCursorModifier === 'alt', scrollback: config.scrollback, theme: this._getXtermTheme(), @@ -707,7 +714,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._setAriaLabel(xterm, this._instanceId, this._title); - xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { // Disable all input if the terminal is exiting if (this._isExiting) { @@ -812,11 +818,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } else { this._terminalShellTypeContextKey.reset(); } - this._onFocused.fire(this); + this._onDidFocus.fire(this); })); this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => { this._terminalFocusContextKey.reset(); + this._onDidBlur.fire(this); this._refreshSelectionContextKey(); })); @@ -1070,6 +1077,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._wrapperElement.classList.toggle('active', visible); } if (visible && this._xterm && this._xtermCore) { + // Resize to re-evaluate dimensions, this will ensure when switching to a terminal it is + // using the most up to date dimensions (eg. when terminal is created in the background + // using cached dimensions of a split terminal). + this._resize(); + // Trigger a manual scroll event which will sync the viewport and scroll bar. This is // necessary if the number of rows in the terminal has decreased while it was in the // background since scrollTop changes take no effect but the terminal's position does @@ -1150,6 +1162,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e, true)); this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e)); + this._processManager.onProcessDidChangeHasChildProcesses(e => this._onDidChangeHasChildProcesses.fire(e)); this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e)); this._processManager.onProcessShellTypeChanged(type => this.setShellType(type)); this._processManager.onPtyDisconnect(() => { @@ -1171,8 +1184,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this._isDisposed) { return; } + + // Re-evaluate dimensions if the container has been set since the xterm instance was created + if (this._container && this._cols === 0 && this._rows === 0) { + this._initDimensions(); + this._xterm?.resize(this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows); + } + const hadIcon = !!this.shellLaunchConfig.icon; - await this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()).then(error => { + await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, this._accessibilityService.isScreenReaderOptimized()).then(error => { if (error) { this._onProcessExit(error); } @@ -1372,7 +1392,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Set the new shell launch config this._shellLaunchConfig = shell; // Must be done before calling _createProcess() - this._processManager.relaunch(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized(), reset); + this._processManager.relaunch(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, this._accessibilityService.isScreenReaderOptimized(), reset); // Set title again as when creating the first process if (this._shellLaunchConfig.name) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index d3d02a6dd4440ece7ab5b3847e2bb93a969bd774..fd5bee18e79c4dd64f992c6c5f56f64fbea4071f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -71,6 +71,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce private _extEnvironmentVariableCollection: IMergedEnvironmentVariableCollection | undefined; private _ackDataBufferer: AckDataBufferer; private _hasWrittenData: boolean = false; + private _hasChildProcesses: boolean = false; private _ptyResponsiveListener: IDisposable | undefined; private _ptyListenersAttached: boolean = false; private _dataFilter: SeamlessRelaunchDataFilter; @@ -81,34 +82,37 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce private _isScreenReaderModeEnabled: boolean = false; private readonly _onPtyDisconnect = this._register(new Emitter()); - get onPtyDisconnect(): Event { return this._onPtyDisconnect.event; } + readonly onPtyDisconnect = this._onPtyDisconnect.event; private readonly _onPtyReconnect = this._register(new Emitter()); - get onPtyReconnect(): Event { return this._onPtyReconnect.event; } + readonly onPtyReconnect = this._onPtyReconnect.event; private readonly _onProcessReady = this._register(new Emitter()); - get onProcessReady(): Event { return this._onProcessReady.event; } + readonly onProcessReady = this._onProcessReady.event; private readonly _onProcessStateChange = this._register(new Emitter()); - get onProcessStateChange(): Event { return this._onProcessStateChange.event; } + readonly onProcessStateChange = this._onProcessStateChange.event; private readonly _onBeforeProcessData = this._register(new Emitter()); - get onBeforeProcessData(): Event { return this._onBeforeProcessData.event; } + readonly onBeforeProcessData = this._onBeforeProcessData.event; private readonly _onProcessData = this._register(new Emitter()); - get onProcessData(): Event { return this._onProcessData.event; } + readonly onProcessData = this._onProcessData.event; private readonly _onProcessTitle = this._register(new Emitter()); - get onProcessTitle(): Event { return this._onProcessTitle.event; } + readonly onProcessTitle = this._onProcessTitle.event; private readonly _onProcessShellTypeChanged = this._register(new Emitter()); - get onProcessShellTypeChanged(): Event { return this._onProcessShellTypeChanged.event; } + readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; private readonly _onProcessExit = this._register(new Emitter()); - get onProcessExit(): Event { return this._onProcessExit.event; } + readonly onProcessExit = this._onProcessExit.event; private readonly _onProcessOverrideDimensions = this._register(new Emitter()); - get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter()); - get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessOverrideShellLaunchConfig.event; } + readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); + readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; + private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter()); + readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event; private readonly _onEnvironmentVariableInfoChange = this._register(new Emitter()); - get onEnvironmentVariableInfoChanged(): Event { return this._onEnvironmentVariableInfoChange.event; } + readonly onEnvironmentVariableInfoChanged = this._onEnvironmentVariableInfoChange.event; get persistentProcessId(): number | undefined { return this._process?.id; } get shouldPersist(): boolean { return this._process ? this._process.shouldPersist : false; } get hasWrittenData(): boolean { return this._hasWrittenData; } + get hasChildProcesses(): boolean { return this._hasChildProcesses; } private readonly _localTerminalService?: ILocalTerminalService; @@ -325,7 +329,13 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._processListeners.push(newProcess.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e))); } if (newProcess.onProcessResolvedShellLaunchConfig) { - this._processListeners.push(newProcess.onProcessResolvedShellLaunchConfig(e => this._onProcessOverrideShellLaunchConfig.fire(e))); + this._processListeners.push(newProcess.onProcessResolvedShellLaunchConfig(e => this._onProcessResolvedShellLaunchConfig.fire(e))); + } + if (newProcess.onDidChangeHasChildProcesses) { + this._processListeners.push(newProcess.onDidChangeHasChildProcesses(e => { + this._hasChildProcesses = e; + this._onProcessDidChangeHasChildProcesses.fire(e); + })); } setTimeout(() => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index cd134fe03259a96205953c459f94064aa398651b..12134389fd3bc4dd035966368500959ec303fa94 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -30,6 +30,7 @@ import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { IconDefinition } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys'; import { IEditableData, IViewsService } from 'vs/workbench/common/views'; import { IRemoteTerminalService, ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalFindHost, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalProfileProvider, ITerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -158,6 +159,7 @@ export class TerminalService implements ITerminalService { @IEditorResolverService editorResolverService: IEditorResolverService, @IExtensionService private readonly _extensionService: IExtensionService, @INotificationService private readonly _notificationService: INotificationService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @optional(ILocalTerminalService) localTerminalService: ILocalTerminalService ) { this._localTerminalService = localTerminalService; @@ -258,6 +260,16 @@ export class TerminalService implements ITerminalService { ? this._localTerminalsInitPromise = this._reconnectToLocalTerminals() : Promise.resolve(); this._primaryOffProcessTerminalService = !!this._environmentService.remoteAuthority ? this._remoteTerminalService : this._localTerminalService; + this._primaryOffProcessTerminalService.onDidRequestDetach(async (e) => { + if (e.workspaceId && this._workspaceContextService.getWorkspace().id === e.workspaceId) { + const instanceToDetach = this.getInstanceFromId(e.instanceId); + const persistentProcessId = instanceToDetach?.persistentProcessId; + if (persistentProcessId && !instanceToDetach.shellLaunchConfig.isFeatureTerminal && !instanceToDetach.shellLaunchConfig.customPtyImplementation) { + await instanceToDetach.detachFromProcess(); + await this._primaryOffProcessTerminalService?.acceptDetachedInstance(e.requestId, persistentProcessId); + } + } + }); initPromise.then(() => this._setConnected()); // Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual @@ -270,6 +282,10 @@ export class TerminalService implements ITerminalService { timeout(0).then(() => this._instantiationService.createInstance(TerminalEditorStyle, document.head)); } + getOffProcessTerminalService(): IOffProcessTerminalService | undefined { + return this._primaryOffProcessTerminalService; + } + private _forwardInstanceHostEvents(host: ITerminalInstanceHost) { host.onDidChangeInstances(this._onDidChangeInstances.fire, this._onDidChangeInstances); host.onDidDisposeInstance(this._onDidDisposeInstance.fire, this._onDidDisposeInstance); @@ -312,7 +328,11 @@ export class TerminalService implements ITerminalService { } async safeDisposeTerminal(instance: ITerminalInstance): Promise { - if (this.configHelper.config.confirmOnExit) { + // Confirm on kill in the editor is handled by the editor input + if (instance.target !== TerminalLocation.Editor && + instance.hasChildProcesses && + (this.configHelper.config.confirmOnKill === 'panel' || this.configHelper.config.confirmOnKill === 'always')) { + const notConfirmed = await this._showTerminalCloseConfirmation(true); if (notConfirmed) { return; @@ -483,8 +503,14 @@ export class TerminalService implements ITerminalService { } const shouldPersistTerminals = this._configHelper.config.enablePersistentSessions && reason === ShutdownReason.RELOAD; - if (this.configHelper.config.confirmOnExit && !shouldPersistTerminals) { - return this._onBeforeShutdownAsync(); + if (!shouldPersistTerminals) { + const hasDirtyInstances = ( + (this.configHelper.config.confirmOnExit === 'always' && this.instances.length > 0) || + (this.configHelper.config.confirmOnExit === 'hasChildProcesses' && this.instances.some(e => e.hasChildProcesses)) + ); + if (hasDirtyInstances) { + return this._onBeforeShutdownAsync(); + } } this._isShuttingDown = true; @@ -722,7 +748,7 @@ export class TerminalService implements ITerminalService { } })); instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onDidMaxiumumDimensionsChange.fire(instance))); - instance.addDisposable(instance.onFocus(this._onDidChangeActiveInstance.fire, this._onDidChangeActiveInstance)); + instance.addDisposable(instance.onDidFocus(this._onDidChangeActiveInstance.fire, this._onDidChangeActiveInstance)); instance.addDisposable(instance.onRequestAddInstanceToGroup(e => { const instanceId = this._getInstanceIdFromUri(e.uri); if (instanceId === undefined) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 7772509be3615ad678cc55c7bbd45944b11a669a..f5c14fea2a4129aa0a09675e0edcfd680bf5df4c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IS_SPLIT_TERMINAL_CONTEXT_KEY, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IOffProcessTerminalService, IS_SPLIT_TERMINAL_CONTEXT_KEY, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { Codicon } from 'vs/base/common/codicons'; import { Action } from 'vs/base/common/actions'; @@ -44,6 +44,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { containsDragType } from 'vs/workbench/browser/dnd'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IProcessDetails } from 'vs/platform/terminal/common/terminalProcess'; const $ = DOM.$; @@ -536,17 +538,20 @@ class TerminalTabsAccessibilityProvider implements IListAccessibilityProvider { private _autoFocusInstance: ITerminalInstance | undefined; private _autoFocusDisposable: IDisposable = Disposable.None; - + private _offProcessTerminalService: IOffProcessTerminalService | undefined; constructor( @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, - @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService - ) { } + @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService + ) { + this._offProcessTerminalService = _terminalService.getOffProcessTerminalService(); + } getDragURI(instance: ITerminalInstance): string | null { return URI.from({ scheme: Schemas.vscodeTerminal, - path: `/${instance.instanceId.toString()}` + path: `/${this._workspaceContextService.getWorkspace().id}/${instance.instanceId.toString()}` }).toString(); } @@ -607,22 +612,36 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { }; } - drop(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { + async drop(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): Promise { this._autoFocusDisposable.dispose(); this._autoFocusInstance = undefined; let sourceInstances: ITerminalInstance[] | undefined; - const terminalResources = originalEvent.dataTransfer?.getData(DataTransfers.TERMINALS); - if (terminalResources) { + const terminalResources = originalEvent.dataTransfer?.getData(DataTransfers.RESOURCES); + const terminals = originalEvent.dataTransfer?.getData(DataTransfers.TERMINALS); + let promises: Promise[] = []; + if (terminals && terminalResources) { const json = JSON.parse(terminalResources); for (const entry of json) { const uri = URI.parse(entry); - const instance = this._terminalService.instances.find(e => e.resource.path === uri.path); - if (instance) { - sourceInstances = [instance]; - this._terminalService.moveToTerminalView(instance); + const [, workspaceId, instanceId] = uri.path.split('/'); + if (workspaceId && instanceId) { + const instance = this._terminalService.instances.find(e => e.resource.path === instanceId); + if (instance) { + sourceInstances = [instance]; + this._terminalService.moveToTerminalView(instance); + } else if (this._offProcessTerminalService && workspaceId !== this._workspaceContextService.getWorkspace().id) { + promises.push(this._offProcessTerminalService.requestDetachInstance(workspaceId, Number.parseInt(instanceId))); + } } } + let processes = await Promise.all(promises); + processes = processes.filter(p => p !== undefined); + for (const attachPersistentProcess of processes) { + const instance = this._terminalService.createTerminal({ config: { attachPersistentProcess } }); + this._terminalService.setActiveInstance(instance); + } + return; } if (sourceInstances === undefined) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 2a8c82a8ce90070aae57d6f8cdc939f8220cf063..fa857884047055850be7d0d7ea45af93e038618a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -468,7 +468,10 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { // Middle click kills this._elementDisposables.push(dom.addDisposableListener(this.element, dom.EventType.AUXCLICK, e => { if (e.button === 1) { - this._terminalGroupService.activeInstance?.dispose(); + const instance = this._terminalGroupService.activeInstance; + if (instance) { + this._terminalService.safeDisposeTerminal(instance); + } e.preventDefault(); } })); diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index e42b31dc91bf2c46d94224928bacc6b27d242a2d..35373984be93ec88806eeabf334c6aab84a4426d 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -114,9 +114,15 @@ export class RemoteTerminalChannelClient { get onProcessOrphanQuestion(): Event<{ id: number }> { return this._channel.listen<{ id: number }>('$onProcessOrphanQuestion'); } + get onProcessDidChangeHasChildProcesses(): Event<{ id: number, event: boolean }> { + return this._channel.listen<{ id: number, event: boolean }>('$onProcessDidChangeHasChildProcesses'); + } get onExecuteCommand(): Event<{ reqId: number, commandId: string, commandArgs: any[] }> { return this._channel.listen<{ reqId: number, commandId: string, commandArgs: any[] }>('$onExecuteCommand'); } + get onDidRequestDetach(): Event<{ requestId: number, workspaceId: string, instanceId: number }> { + return this._channel.listen<{ requestId: number, workspaceId: string, instanceId: number }>('$onDidRequestDetach'); + } constructor( private readonly _remoteAuthority: string, @@ -195,6 +201,12 @@ export class RemoteTerminalChannelClient { return await this._channel.call('$createProcess', args); } + requestDetachInstance(workspaceId: string, instanceId: number): Promise { + return this._channel.call('$requestDetachInstance', [workspaceId, instanceId]); + } + acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise { + return this._channel.call('$acceptDetachedInstance', [requestId, persistentProcessId]); + } attachToProcess(id: number): Promise { return this._channel.call('$attachToProcess', [id]); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index ad46cff03dc0af49a5e7135deedbd6837875db6a..c14894a4fbf101e1e9c1f1a532a81d454199fdae 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -24,6 +24,9 @@ export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey(Te /** A context key that is set when the integrated terminal has focus. */ export const KEYBINDING_CONTEXT_TERMINAL_FOCUS = new RawContextKey(TerminalContextKey.Focus, false, nls.localize('terminalFocusContextKey', "Whether the terminal is focused")); +/** A context key that is set when a terminal editor has focus. */ +export const KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS = new RawContextKey(TerminalContextKey.EditorFocus, false, nls.localize('terminalEditorFocusContextKey', "Whether a terminal in the editor area is focused")); + /** A context key that is set to the current number of integrated terminals in the terminal groups. */ export const KEYBINDING_CONTEXT_GROUP_TERMINAL_COUNT = new RawContextKey(TerminalContextKey.Count, 0, nls.localize('terminalCountContextKey', "The current number of terminals")); @@ -142,6 +145,8 @@ export interface IOffProcessTerminalService { */ onPtyHostRestart: Event; + onDidRequestDetach: Event<{ requestId: number, workspaceId: string, instanceId: number }>; + attachToProcess(id: number): Promise; listProcesses(): Promise; getDefaultSystemShell(osOverride?: OperatingSystem): Promise; @@ -154,6 +159,8 @@ export interface IOffProcessTerminalService { updateIcon(id: number, icon: TerminalIcon, color?: string): Promise; getTerminalLayoutInfo(): Promise; reduceConnectionGraceTime(): Promise; + requestDetachInstance(workspaceId: string, instanceId: number): Promise; + acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise; } export const ILocalTerminalService = createDecorator('localTerminalService'); @@ -169,6 +176,9 @@ export interface ITerminalProfiles { windows: { [key: string]: ITerminalProfileObject }; } +export type ConfirmOnKill = 'never' | 'always' | 'editor' | 'panel'; +export type ConfirmOnExit = 'never' | 'always' | 'hasChildProcesses'; + export interface ITerminalConfiguration { shell: { linux: string | null; @@ -218,7 +228,8 @@ export interface ITerminalConfiguration { allowChords: boolean; allowMnemonics: boolean; cwd: string; - confirmOnExit: boolean; + confirmOnExit: ConfirmOnExit; + confirmOnKill: ConfirmOnKill; enableBell: boolean; env: { linux: { [key: string]: string }; @@ -321,8 +332,8 @@ export interface ITerminalProcessManager extends IDisposable { readonly persistentProcessId: number | undefined; readonly shouldPersist: boolean; readonly isDisconnected: boolean; - /** Whether the process has had data written to it yet. */ readonly hasWrittenData: boolean; + readonly hasChildProcesses: boolean; readonly onPtyDisconnect: Event; readonly onPtyReconnect: Event; @@ -335,6 +346,7 @@ export interface ITerminalProcessManager extends IDisposable { readonly onProcessExit: Event; readonly onProcessOverrideDimensions: Event; readonly onProcessResolvedShellLaunchConfig: Event; + readonly onProcessDidChangeHasChildProcesses: Event; readonly onEnvironmentVariableInfoChanged: Event; dispose(immediate?: boolean): void; diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ca53b3f19c49069b6d5cfcc5783c56f1bafcee40..7e5cd9dfccf029f7d1c58680ec0e44c07e585b38 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -266,9 +266,27 @@ const terminalConfiguration: IConfigurationNode = { default: undefined }, [TerminalSettingId.ConfirmOnExit]: { - description: localize('terminal.integrated.confirmOnExit', "Controls whether to confirm on exit if there are active terminal sessions."), - type: 'boolean', - default: false + description: localize('terminal.integrated.confirmOnExit', "Controls whether to confirm when the window closes if there are active terminal sessions."), + type: 'string', + enum: ['never', 'always', 'hasChildProcesses'], + enumDescriptions: [ + localize('terminal.integrated.confirmOnExit.never', "Never confirm."), + localize('terminal.integrated.confirmOnExit.always', "Always confirm if there are terminals."), + localize('terminal.integrated.confirmOnExit.hasChildProcesses', "Confirm if there are any terminals that has child processes."), + ], + default: 'hasChildProcesses' + }, + [TerminalSettingId.ConfirmOnKill]: { + description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as dirty when they have child processes."), + type: 'string', + enum: ['never', 'editor', 'panel', 'always'], + enumDescriptions: [ + localize('terminal.integrated.confirmOnKill.never', "Never confirm."), + localize('terminal.integrated.confirmOnKill.editor', "Confirm if the terminal is in the editor."), + localize('terminal.integrated.confirmOnKill.panel', "Confirm if the terminal is in the panel."), + localize('terminal.integrated.confirmOnKill.always', "Confirm if the terminal is either in the editor or panel."), + ], + default: 'editor' }, [TerminalSettingId.EnableBell]: { description: localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled, this shows up as a visual bell next to the terminal's name."), diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index b2e813eda185e5329184acb9dd0ee62e3ce5aa88..8d6e76ff88d67ffb29d7b39f39b9f0b6c151915e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -10,6 +10,7 @@ export const enum TerminalContextKey { TabsNarrow = 'isTerminalTabsNarrow', ProcessSupported = 'terminalProcessSupported', Focus = 'terminalFocus', + EditorFocus = 'terminalEditorFocus', TabsFocus = 'terminalTabsFocus', TabsMouse = 'terminalTabsMouse', AltBufferActive = 'terminalAltBufferActive', diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index d25479bb980dfff7321fde092f8dcb6ffb486f89..f810b739cc4eb0fe313024cfafba8c8dfc22ace0 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -32,6 +32,8 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; private readonly _onProcessShellTypeChanged = this._register(new Emitter()); readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; + private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); + readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; constructor( readonly id: number, @@ -106,6 +108,9 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { handleResolvedShellLaunchConfig(e: IShellLaunchConfig) { this._onProcessResolvedShellLaunchConfig.fire(e); } + handleDidChangeHasChildProcesses(e: boolean) { + this._onDidChangeHasChildProcesses.fire(e); + } async handleReplay(e: IPtyHostProcessReplayEvent) { try { diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index dec25098ca25a434167f65da68fc87b0c4543c34..96e7de8cc77dc1ae8deef1fef2163a45c927e0e0 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -36,6 +36,8 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe readonly onPtyHostResponsive = this._onPtyHostResponsive.event; private readonly _onPtyHostRestart = this._register(new Emitter()); readonly onPtyHostRestart = this._onPtyHostRestart.event; + private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number, workspaceId: string, instanceId: number }>()); + readonly onDidRequestDetach = this._onDidRequestDetach.event; constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -63,8 +65,10 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe this._localPtyService.onProcessTitleChanged(e => this._ptys.get(e.id)?.handleTitleChanged(e.event)); this._localPtyService.onProcessOverrideDimensions(e => this._ptys.get(e.id)?.handleOverrideDimensions(e.event)); this._localPtyService.onProcessResolvedShellLaunchConfig(e => this._ptys.get(e.id)?.handleResolvedShellLaunchConfig(e.event)); + this._localPtyService.onProcessDidChangeHasChildProcesses(e => this._ptys.get(e.id)?.handleDidChangeHasChildProcesses(e.event)); this._localPtyService.onProcessReplay(e => this._ptys.get(e.id)?.handleReplay(e.event)); this._localPtyService.onProcessOrphanQuestion(e => this._ptys.get(e.id)?.handleOrphanQuestion()); + this._localPtyService.onDidRequestDetach(e => this._onDidRequestDetach.fire(e)); // Attach pty host listeners if (this._localPtyService.onPtyHostExit) { @@ -121,6 +125,15 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe })); } } + + async requestDetachInstance(workspaceId: string, instanceId: number): Promise { + return this._localPtyService.requestDetachInstance(workspaceId, instanceId); + } + + async acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise { + return this._localPtyService.acceptDetachedInstance(requestId, persistentProcessId); + } + async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise { await this._localPtyService.updateTitle(id, title, titleSource); } diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts index 8aed628b719e981e79660a3bff83bf7ba06cf0e9..9f8446841dd39dbb93f5c919470b8b4800a3d7b7 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Iterable } from 'vs/base/common/iterator'; import { TestExplorerTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections'; import { flatTestItemDelimiter } from 'vs/workbench/contrib/testing/browser/explorerProjections/display'; import { HierarchicalByLocationProjection as HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation'; import { ByLocationTestItemElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes'; import { NodeRenderDirective } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper'; -import { InternalTestItem, ITestItemUpdate } from 'vs/workbench/contrib/testing/common/testCollection'; +import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; @@ -18,20 +17,16 @@ import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; */ export const enum ListElementType { /** The element is a leaf test that should be shown in the list */ - TestLeaf, + Leaf, /** The element is not runnable, but doesn't have any nested leaf tests */ - BranchWithLeaf, - /** The element has nested leaf tests */ - BranchWithoutLeaf, - /** State not yet computed */ - Unset, + Branch, } /** * Version of the HierarchicalElement that is displayed as a list. */ export class ByNameTestItemElement extends ByLocationTestItemElement { - public elementType: ListElementType = ListElementType.Unset; + public elementType: ListElementType = ListElementType.Leaf; public readonly isTestRoot = !this.actualParent; public readonly actualChildren = new Set(); @@ -51,22 +46,10 @@ export class ByNameTestItemElement extends ByLocationTestItemElement { internal: InternalTestItem, parentItem: null | ByLocationTestItemElement, addedOrRemoved: (n: TestExplorerTreeElement) => void, - private readonly actualParent?: ByNameTestItemElement, + public readonly actualParent?: ByNameTestItemElement, ) { super(internal, parentItem, addedOrRemoved); actualParent?.addChild(this); - this.updateLeafTestState(); - } - - /** - * @override - */ - public override update(patch: ITestItemUpdate) { - super.update(patch); - - if (patch.item?.runnable !== undefined) { - this.updateLeafTestState(); - } } /** @@ -78,32 +61,10 @@ export class ByNameTestItemElement extends ByLocationTestItemElement { private removeChild(element: ByNameTestItemElement) { this.actualChildren.delete(element); - this.updateLeafTestState(); } private addChild(element: ByNameTestItemElement) { this.actualChildren.add(element); - this.updateLeafTestState(); - } - - /** - * Updates the test leaf state for this node. Should be called when a child - * or this node is modified. Note that we never need to look at the children - * here, the children will already be leaves, or not. - */ - private updateLeafTestState() { - const newType = Iterable.some(this.actualChildren, c => c.elementType !== ListElementType.BranchWithoutLeaf) - ? ListElementType.BranchWithLeaf - : this.test.item.runnable - ? ListElementType.TestLeaf - : ListElementType.BranchWithoutLeaf; - - if (newType !== this.elementType) { - this.elementType = newType; - this.addedOrRemoved(this); - } - - this.actualParent?.updateLeafTestState(); } } @@ -118,7 +79,7 @@ export class HierarchicalByNameProjection extends HierarchicalByLocationProjecti const originalRenderNode = this.renderNode.bind(this); this.renderNode = (node, recurse) => { - if (node instanceof ByNameTestItemElement && node.elementType !== ListElementType.TestLeaf && !node.isTestRoot) { + if (node instanceof ByNameTestItemElement && node.elementType !== ListElementType.Leaf && !node.isTestRoot) { return NodeRenderDirective.Concat; } @@ -136,16 +97,21 @@ export class HierarchicalByNameProjection extends HierarchicalByLocationProjecti */ protected override createItem(item: InternalTestItem): ByLocationTestItemElement { const actualParent = item.parent ? this.items.get(item.parent) as ByNameTestItemElement : undefined; - if (actualParent) { - return new ByNameTestItemElement( - item, - actualParent.parent as ByNameTestItemElement || actualParent, - r => this.changes.addedOrRemoved(r), - actualParent, - ); + if (!actualParent) { + return new ByNameTestItemElement(item, null, r => this.changes.addedOrRemoved(r)); } - return new ByNameTestItemElement(item, null, r => this.changes.addedOrRemoved(r)); + if (actualParent.elementType === ListElementType.Leaf) { + actualParent.elementType = ListElementType.Branch; + this.changes.addedOrRemoved(actualParent); + } + + return new ByNameTestItemElement( + item, + actualParent.parent as ByNameTestItemElement || actualParent, + r => this.changes.addedOrRemoved(r), + actualParent, + ); } /** @@ -153,7 +119,13 @@ export class HierarchicalByNameProjection extends HierarchicalByLocationProjecti */ protected override unstoreItem(items: Map, item: ByLocationTestItemElement) { const treeChildren = super.unstoreItem(items, item); + if (item instanceof ByNameTestItemElement) { + if (item.actualParent && item.actualParent.actualChildren.size === 1) { + item.actualParent.elementType = ListElementType.Leaf; + this.changes.addedOrRemoved(item.actualParent); + } + item.remove(); return item.actualChildren; } diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts index 5136d861d793fdd7abdf00a2f485a38b81422ec5..070f01b9bb64c117a472369f6cdb7adc77360ea2 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts @@ -67,14 +67,9 @@ export interface IActionableTestTreeElement { depth: number; /** - * Tests to debug when the 'debug' context action is taken on this item. + * Iterable of the tests this element contains. */ - debuggable: Iterable; - - /** - * Tests to run when the 'debug' context action is taken on this item. - */ - runnable: Iterable; + tests: Iterable; /** * State to show on the item. This is generally the item's computed state @@ -113,22 +108,8 @@ export class TestItemTreeElement implements IActionableTestTreeElement { */ public depth: number = this.parent ? this.parent.depth + 1 : 0; - /** - * @inheritdoc - */ - public get runnable() { - return this.test.item.runnable - ? Iterable.single(identifyTest(this.test)) - : Iterable.empty(); - } - - /** - * @inheritdoc - */ - public get debuggable() { - return this.test.item.debuggable - ? Iterable.single(identifyTest(this.test)) - : Iterable.empty(); + public get tests() { + return Iterable.single(identifyTest(this.test)); } public get description() { diff --git a/src/vs/workbench/contrib/testing/browser/icons.ts b/src/vs/workbench/contrib/testing/browser/icons.ts index 1ea865256b595edecc92058f09e86590d8a26b5e..42f199ab3013e8227c697dbdc5f357a3660f1591 100644 --- a/src/vs/workbench/contrib/testing/browser/icons.ts +++ b/src/vs/workbench/contrib/testing/browser/icons.ts @@ -13,7 +13,9 @@ import { testingColorRunAction, testMessageSeverityColors, testStatesToIconColor export const testingViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.')); export const testingRunIcon = registerIcon('testing-run-icon', Codicon.run, localize('testingRunIcon', 'Icon of the "run test" action.')); export const testingRunAllIcon = registerIcon('testing-run-all-icon', Codicon.runAll, localize('testingRunAllIcon', 'Icon of the "run all tests" action.')); -export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAlt, localize('testingDebugIcon', 'Icon of the "debug test" action.')); +// todo: https://github.com/microsoft/vscode-codicons/issues/72 +export const testingDebugAllIcon = registerIcon('testing-debug-all-icon', Codicon.debugAltSmall, localize('testingDebugAllIcon', 'Icon of the "debug all tests" action.')); +export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAltSmall, localize('testingDebugIcon', 'Icon of the "debug test" action.')); export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.debugStop, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.')); export const testingFilterIcon = registerIcon('testing-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the testing view.')); export const testingAutorunIcon = registerIcon('testing-autorun', Codicon.debugRerun, localize('autoRunIcon', 'Icon for the \'Autorun\' toggle in the testing view.')); @@ -22,6 +24,8 @@ export const testingHiddenIcon = registerIcon('testing-hidden', Codicon.eyeClose export const testingShowAsList = registerIcon('testing-show-as-list-icon', Codicon.listTree, localize('testingShowAsList', 'Icon shown when the test explorer is disabled as a tree.')); export const testingShowAsTree = registerIcon('testing-show-as-list-icon', Codicon.listFlat, localize('testingShowAsTree', 'Icon shown when the test explorer is disabled as a list.')); +export const testingUpdateConfiguration = registerIcon('testing-update-configuration', Codicon.gear, localize('testingUpdateConfiguration', 'Icon shown to update test configurations.')); + export const testingStatesToIcons = new Map([ [TestResultState.Errored, registerIcon('testing-error-icon', Codicon.issues, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))], [TestResultState.Failed, registerIcon('testing-failed-icon', Codicon.error, localize('testingFailedIcon', 'Icon shown for tests that failed.'))], diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index be07f2f552236e25aed81c835b4c5193116eb926..a4a273e3997fe68ca243a0b9ea9eba0d50ea7a09 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -26,10 +26,11 @@ import { REVEAL_IN_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browse import { IActionableTestTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; import * as icons from 'vs/workbench/contrib/testing/browser/icons'; import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter'; -import { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; +import type { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService'; import { TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants'; -import { identifyTest, InternalTestItem, ITestIdWithSrc, ITestItem, TestIdPath } from 'vs/workbench/contrib/testing/common/testCollection'; +import { identifyTest, InternalTestItem, ITestIdWithSrc, ITestItem, ITestRunConfiguration, TestIdPath, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { ITestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; @@ -46,13 +47,16 @@ const enum ActionOrder { // Navigation: Run = 10, Debug, + Coverage, + RunUsing, AutoRun, - Collapse, // Submenu: + Collapse, DisplayMode, Sort, - Refresh, + GoToTest, + HideTest, } const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.providerCount.key, 0); @@ -74,7 +78,7 @@ export class HideTestAction extends Action2 { const service = accessor.get(ITestService); for (const element of elements) { if (element instanceof TestItemTreeElement) { - service.setTestExcluded(element.test.item.extId, true); + service.excluded.toggle(identifyTest(element.test), true); } } return Promise.resolve(); @@ -89,6 +93,7 @@ export class UnhideTestAction extends Action2 { title: localize('unhideTest', 'Unhide Test'), menu: { id: MenuId.TestItem, + order: ActionOrder.HideTest, when: TestingContextKeys.testItemIsHidden.isEqualTo(true) }, }); @@ -98,7 +103,7 @@ export class UnhideTestAction extends Action2 { const service = accessor.get(ITestService); for (const element of elements) { if (element instanceof TestItemTreeElement) { - service.setTestExcluded(element.test.item.extId, false); + service.excluded.toggle(identifyTest(element.test), false); } } return Promise.resolve(); @@ -123,12 +128,51 @@ export class DebugAction extends Action2 { public override run(acessor: ServicesAccessor, ...elements: IActionableTestTreeElement[]): Promise { return acessor.get(ITestService).runTests({ - tests: [...Iterable.concatNested(elements.map(e => e.debuggable))], - debug: true, + tests: [...Iterable.concatNested(elements.map(e => e.tests))], + group: TestRunConfigurationBitset.Debug, }); } } +export class RunUsingProfileAction extends Action2 { + public static readonly ID = 'testing.runUsing'; + constructor() { + super({ + id: RunUsingProfileAction.ID, + title: localize('testing.runUsing', 'Execute Using Profile...'), + icon: icons.testingDebugIcon, + menu: { + id: MenuId.TestItem, + order: ActionOrder.RunUsing, + when: TestingContextKeys.hasNonDefaultProfile.isEqualTo(true), + }, + }); + } + + public override async run(acessor: ServicesAccessor, ...elements: IActionableTestTreeElement[]): Promise { + const testElements = elements.filter((e): e is TestItemTreeElement => e instanceof TestItemTreeElement); + if (testElements.length === 0) { + return; + } + + const commandService = acessor.get(ICommandService); + const testService = acessor.get(ITestService); + const controllerId = testElements[0].test.controllerId; + const profile: ITestRunConfiguration | undefined = await commandService.executeCommand('vscode.pickTestProfile', { onlyControllerId: controllerId }); + if (!profile) { + return; + } + + testService.runResolvedTests({ + targets: [{ + profileGroup: profile.group, + profileId: profile.profileId, + controllerId: profile.controllerId, + testIds: testElements.filter(t => controllerId === t.test.controllerId).map(t => t.test.item.extId) + }] + }); + } +} export class RunAction extends Action2 { public static readonly ID = 'testing.run'; @@ -151,14 +195,72 @@ export class RunAction extends Action2 { */ public override run(acessor: ServicesAccessor, ...elements: IActionableTestTreeElement[]): Promise { return acessor.get(ITestService).runTests({ - tests: [...Iterable.concatNested(elements.map(e => e.runnable))], - debug: false, + tests: [...Iterable.concatNested(elements.map(e => e.tests))], + group: TestRunConfigurationBitset.Run, + }); + } +} + +export class SelectDefaultTestProfiles extends Action2 { + public static readonly ID = 'testing.selectDefaultTestProfiles'; + constructor() { + super({ + id: SelectDefaultTestProfiles.ID, + title: localize('testing.selectDefaultTestProfiles', 'Select Default Profile'), + icon: icons.testingUpdateConfiguration, + category, + }); + } + + public override async run(acessor: ServicesAccessor, onlyGroup: TestRunConfigurationBitset) { + const commands = acessor.get(ICommandService); + const testConfigurationService = acessor.get(ITestConfigurationService); + const configurations = await commands.executeCommand('vscode.pickMultipleTestProfiles', { + showConfigureButtons: false, + selected: testConfigurationService.getGroupDefaultConfigurations(onlyGroup), + onlyGroup, + }); + + if (configurations?.length) { + testConfigurationService.setGroupDefaultConfigurations(onlyGroup, configurations); + } + } +} + +export class ConfigureTestProfilesAction extends Action2 { + public static readonly ID = 'testing.configureProfile'; + constructor() { + super({ + id: ConfigureTestProfilesAction.ID, + title: localize('testing.configureProfile', 'Configure Test Profiles'), + icon: icons.testingUpdateConfiguration, + f1: true, + category, + menu: { + id: MenuId.CommandPalette, + when: TestingContextKeys.hasConfigurableConfig.isEqualTo(true), + }, }); } + + public override async run(acessor: ServicesAccessor, onlyGroup?: TestRunConfigurationBitset) { + const commands = acessor.get(ICommandService); + const testConfigurationService = acessor.get(ITestConfigurationService); + const configuration = await commands.executeCommand('vscode.pickTestProfile', { + placeholder: localize('configureProfile', 'Select a profile to update'), + showConfigureButtons: false, + onlyConfigurable: true, + onlyGroup, + }); + + if (configuration) { + testConfigurationService.configure(configuration.controllerId, configuration.profileId); + } + } } -abstract class RunOrDebugSelectedAction extends ViewAction { - constructor(id: string, title: string, icon: ThemeIcon, private readonly debug: boolean) { +abstract class ExecuteSelectedAction extends ViewAction { + constructor(id: string, title: string, icon: ThemeIcon, private readonly group: TestRunConfigurationBitset) { super({ id, title, @@ -179,7 +281,7 @@ abstract class RunOrDebugSelectedAction extends ViewAction return Promise.resolve(undefined); } - return accessor.get(ITestService).runTests({ tests, debug: this.debug }); + return accessor.get(ITestService).runTests({ tests, group: this.group }); } private getActionableTests(testService: ITestService, viewModel: TestingExplorerViewModel) { @@ -189,18 +291,16 @@ abstract class RunOrDebugSelectedAction extends ViewAction tests = ([...testService.collection.rootItems].map(identifyTest)); } else { tests = selected - .map(treeElement => treeElement instanceof TestItemTreeElement && this.filter(treeElement.test) ? treeElement.test : undefined) + .map(treeElement => treeElement instanceof TestItemTreeElement ? treeElement.test : undefined) .filter(isDefined) .map(identifyTest); } return tests; } - - protected abstract filter(item: InternalTestItem): boolean; } -export class RunSelectedAction extends RunOrDebugSelectedAction { +export class RunSelectedAction extends ExecuteSelectedAction { public static readonly ID = 'testing.runSelected'; constructor() { @@ -208,35 +308,21 @@ export class RunSelectedAction extends RunOrDebugSelectedAction { RunSelectedAction.ID, localize('runSelectedTests', 'Run Selected Tests'), icons.testingRunIcon, - false, + TestRunConfigurationBitset.Run, ); } - - /** - * @override - */ - public filter({ item }: InternalTestItem) { - return item.runnable; - } } -export class DebugSelectedAction extends RunOrDebugSelectedAction { +export class DebugSelectedAction extends ExecuteSelectedAction { public static readonly ID = 'testing.debugSelected'; constructor() { super( DebugSelectedAction.ID, localize('debugSelectedTests', 'Debug Selected Tests'), icons.testingDebugIcon, - true, + TestRunConfigurationBitset.Debug, ); } - - /** - * @override - */ - public filter({ item }: InternalTestItem) { - return item.debuggable; - } } const showDiscoveringWhile = (progress: IProgressService, task: Promise): Promise => { @@ -250,27 +336,26 @@ const showDiscoveringWhile = (progress: IProgressService, task: Promise): }; abstract class RunOrDebugAllTestsAction extends Action2 { - constructor(id: string, title: string, icon: ThemeIcon, private readonly debug: boolean, private noTestsFoundError: string, keybinding: IAction2Options['keybinding']) { + constructor(options: IAction2Options, private readonly group: TestRunConfigurationBitset, private noTestsFoundError: string) { super({ - id, - title, - icon, + ...options, category, - keybinding, menu: [{ id: MenuId.ViewTitle, - order: debug ? ActionOrder.Debug : ActionOrder.Run, + order: group === TestRunConfigurationBitset.Run + ? ActionOrder.Run + : group === TestRunConfigurationBitset.Debug + ? ActionOrder.Debug + : ActionOrder.Coverage, group: 'navigation', when: ContextKeyAndExpr.create([ ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId), TestingContextKeys.isRunning.isEqualTo(false), - debug - ? TestingContextKeys.hasDebuggableTests.isEqualTo(true) - : TestingContextKeys.hasRunnableTests.isEqualTo(true), + TestingContextKeys.capabilityToContextKey[group].isEqualTo(true), ]) }, { id: MenuId.CommandPalette, - when: hasAnyTestProvider, + when: TestingContextKeys.capabilityToContextKey[group].isEqualTo(true), }] }); } @@ -285,7 +370,7 @@ abstract class RunOrDebugAllTestsAction extends Action2 { return; } - await testService.runTests({ tests: roots.map(identifyTest), debug: this.debug }); + await testService.runTests({ tests: roots.map(identifyTest), group: this.group }); } } @@ -293,15 +378,17 @@ export class RunAllAction extends RunOrDebugAllTestsAction { public static readonly ID = 'testing.runAll'; constructor() { super( - RunAllAction.ID, - localize('runAllTests', 'Run All Tests'), - icons.testingRunAllIcon, - false, - localize('noTestProvider', 'No tests found in this workspace. You may need to install a test provider extension'), { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyCode.KEY_A), - } + id: RunAllAction.ID, + title: localize('runAllTests', 'Run All Tests'), + icon: icons.testingRunAllIcon, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyCode.KEY_A), + }, + }, + TestRunConfigurationBitset.Run, + localize('noTestProvider', 'No tests found in this workspace. You may need to install a test provider extension'), ); } } @@ -310,15 +397,17 @@ export class DebugAllAction extends RunOrDebugAllTestsAction { public static readonly ID = 'testing.debugAll'; constructor() { super( - DebugAllAction.ID, - localize('debugAllTests', 'Debug All Tests'), - icons.testingDebugIcon, - true, - localize('noDebugTestProvider', 'No debuggable tests found in this workspace. You may need to install a test provider extension'), { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_A), - } + id: DebugAllAction.ID, + title: localize('debugAllTests', 'Debug All Tests'), + icon: icons.testingDebugIcon, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_A), + }, + }, + TestRunConfigurationBitset.Debug, + localize('noDebugTestProvider', 'No debuggable tests found in this workspace. You may need to install a test provider extension'), ); } } @@ -503,7 +592,7 @@ export class CollapseAllAction extends ViewAction { menu: { id: MenuId.ViewTitle, order: ActionOrder.Collapse, - group: 'navigation', + group: 'cikkaose', when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId) } }); @@ -517,33 +606,6 @@ export class CollapseAllAction extends ViewAction { } } -export class RefreshTestsAction extends Action2 { - public static readonly ID = 'testing.refreshTests'; - constructor() { - super({ - id: RefreshTestsAction.ID, - title: localize('testing.refresh', "Refresh Tests"), - category, - menu: [{ - id: MenuId.ViewTitle, - order: ActionOrder.Refresh, - group: 'refresh', - when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId) - }, { - id: MenuId.CommandPalette, - when: TestingContextKeys.providerCount.isEqualTo(true), - }], - }); - } - - /** - * @override - */ - public run(accessor: ServicesAccessor) { - accessor.get(ITestService).resubscribeToAllTests(); - } -} - export class ClearTestResultsAction extends Action2 { public static readonly ID = 'testing.clearTestResults'; constructor() { @@ -578,6 +640,7 @@ export class GoToTest extends Action2 { menu: { id: MenuId.TestItem, when: TestingContextKeys.testItemHasUri.isEqualTo(true), + order: ActionOrder.GoToTest, }, keybinding: { weight: KeybindingWeight.EditorContrib - 10, @@ -718,8 +781,8 @@ export class AutoRunOffAction extends ToggleAutoRun { } -abstract class RunOrDebugAtCursor extends Action2 { - constructor(options: IAction2Options) { +abstract class ExecuteTestAtCursor extends Action2 { + constructor(options: IAction2Options, protected readonly group: TestRunConfigurationBitset) { super({ ...options, menu: { @@ -745,7 +808,7 @@ abstract class RunOrDebugAtCursor extends Action2 { await showDiscoveringWhile(accessor.get(IProgressService), (async () => { for await (const test of testsInFile(testService.collection, model.uri)) { - if (this.filter(test) && test.item.range && Range.containsPosition(test.item.range, position)) { + if (test.item.range && Range.containsPosition(test.item.range, position)) { bestNode = test; } } @@ -753,16 +816,15 @@ abstract class RunOrDebugAtCursor extends Action2 { if (bestNode) { - await this.runTest(testService, bestNode); + await testService.runTests({ + group: this.group, + tests: [identifyTest(bestNode)], + }); } } - - protected abstract filter(node: InternalTestItem): boolean; - - protected abstract runTest(service: ITestService, node: InternalTestItem): Promise; } -export class RunAtCursor extends RunOrDebugAtCursor { +export class RunAtCursor extends ExecuteTestAtCursor { public static readonly ID = 'testing.runAtCursor'; constructor() { super({ @@ -774,22 +836,11 @@ export class RunAtCursor extends RunOrDebugAtCursor { when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyCode.KEY_C), }, - }); - } - - protected filter(node: InternalTestItem): boolean { - return node.item.runnable; - } - - protected runTest(service: ITestService, internalTest: InternalTestItem): Promise { - return service.runTests({ - debug: false, - tests: [identifyTest(internalTest)], - }); + }, TestRunConfigurationBitset.Run); } } -export class DebugAtCursor extends RunOrDebugAtCursor { +export class DebugAtCursor extends ExecuteTestAtCursor { public static readonly ID = 'testing.debugAtCursor'; constructor() { super({ @@ -801,28 +852,17 @@ export class DebugAtCursor extends RunOrDebugAtCursor { when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_C), }, - }); - } - - protected filter(node: InternalTestItem): boolean { - return node.item.debuggable; - } - - protected runTest(service: ITestService, internalTest: InternalTestItem): Promise { - return service.runTests({ - debug: true, - tests: [identifyTest(internalTest)], - }); + }, TestRunConfigurationBitset.Debug); } } -abstract class RunOrDebugCurrentFile extends Action2 { - constructor(options: IAction2Options) { +abstract class ExecuteTestsInCurrentFile extends Action2 { + constructor(options: IAction2Options, protected readonly group: TestRunConfigurationBitset) { super({ ...options, menu: { id: MenuId.CommandPalette, - when: hasAnyTestProvider, + when: TestingContextKeys.capabilityToContextKey[group].isEqualTo(true), }, }); } @@ -843,21 +883,20 @@ abstract class RunOrDebugCurrentFile extends Action2 { const demandedUri = model.uri.toString(); for (const test of testService.collection.all) { if (test.item.uri?.toString() === demandedUri) { - return this.runTest(testService, [test]); + return testService.runTests({ + tests: [identifyTest(test)], + group: this.group, + }); } } return undefined; } - - - protected abstract filter(node: InternalTestItem): boolean; - - protected abstract runTest(service: ITestService, node: InternalTestItem[]): Promise; } -export class RunCurrentFile extends RunOrDebugCurrentFile { +export class RunCurrentFile extends ExecuteTestsInCurrentFile { public static readonly ID = 'testing.runCurrentFile'; + constructor() { super({ id: RunCurrentFile.ID, @@ -868,27 +907,13 @@ export class RunCurrentFile extends RunOrDebugCurrentFile { when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyCode.KEY_F), }, - menu: { - id: MenuId.CommandPalette, - when: hasAnyTestProvider, - }, - }); - } - - protected filter(node: InternalTestItem): boolean { - return node.item.runnable; - } - - protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { - return service.runTests({ - debug: false, - tests: internalTests.map(identifyTest), - }); + }, TestRunConfigurationBitset.Run); } } -export class DebugCurrentFile extends RunOrDebugCurrentFile { +export class DebugCurrentFile extends ExecuteTestsInCurrentFile { public static readonly ID = 'testing.debugCurrentFile'; + constructor() { super({ id: DebugCurrentFile.ID, @@ -899,18 +924,7 @@ export class DebugCurrentFile extends RunOrDebugCurrentFile { when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_F), }, - }); - } - - protected filter(node: InternalTestItem): boolean { - return node.item.debuggable; - } - - protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { - return service.runTests({ - debug: true, - tests: internalTests.map(identifyTest) - }); + }, TestRunConfigurationBitset.Debug); } } @@ -941,8 +955,6 @@ abstract class RunOrDebugExtsByPath extends Action2 { protected abstract getTestExtIdsToRun(accessor: ServicesAccessor, ...args: unknown[]): Iterable; - protected abstract filter(node: InternalTestItem): boolean; - protected abstract runTest(service: ITestService, node: readonly InternalTestItem[]): Promise; } @@ -1003,9 +1015,12 @@ abstract class RunOrDebugLastRun extends RunOrDebugExtsByPath { return; } - for (const test of lastResult.tests) { - if (test.direct) { - yield getPathForTestInResult(test, lastResult); + for (const test of lastResult.request.targets) { + for (const testId of test.testIds) { + const test = lastResult.getStateById(testId); + if (test) { + yield getPathForTestInResult(test, lastResult); + } } } } @@ -1025,13 +1040,9 @@ export class ReRunFailedTests extends RunOrDebugFailedTests { }); } - protected filter(node: InternalTestItem): boolean { - return node.item.runnable; - } - protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { return service.runTests({ - debug: false, + group: TestRunConfigurationBitset.Run, tests: internalTests.map(identifyTest), }); } @@ -1051,13 +1062,9 @@ export class DebugFailedTests extends RunOrDebugFailedTests { }); } - protected filter(node: InternalTestItem): boolean { - return node.item.debuggable; - } - protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { return service.runTests({ - debug: true, + group: TestRunConfigurationBitset.Debug, tests: internalTests.map(identifyTest), }); } @@ -1077,13 +1084,9 @@ export class ReRunLastRun extends RunOrDebugLastRun { }); } - protected filter(node: InternalTestItem): boolean { - return node.item.runnable; - } - protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { return service.runTests({ - debug: false, + group: TestRunConfigurationBitset.Debug, tests: internalTests.map(identifyTest), }); } @@ -1103,13 +1106,9 @@ export class DebugLastRun extends RunOrDebugLastRun { }); } - protected filter(node: InternalTestItem): boolean { - return node.item.debuggable; - } - protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { return service.runTests({ - debug: true, + group: TestRunConfigurationBitset.Debug, tests: internalTests.map(identifyTest), }); } @@ -1161,6 +1160,7 @@ export const allTestActions = [ CancelTestRunAction, ClearTestResultsAction, CollapseAllAction, + ConfigureTestProfilesAction, DebugAction, DebugAllAction, DebugAtCursor, @@ -1171,7 +1171,6 @@ export const allTestActions = [ GoToTest, HideTestAction, OpenOutputPeek, - RefreshTestsAction, ReRunFailedTests, ReRunLastRun, RunAction, @@ -1179,7 +1178,9 @@ export const allTestActions = [ RunAtCursor, RunCurrentFile, RunSelectedAction, + RunUsingProfileAction, SearchForTestExtension, + SelectDefaultTestProfiles, ShowMostRecentOutputAction, TestingSortByLocationAction, TestingSortByStatusAction, @@ -1187,5 +1188,3 @@ export const allTestActions = [ TestingViewAsTreeAction, UnhideTestAction, ]; - -export const internalTestActionIds = new Set(allTestActions.map(a => a.ID)); diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index a2035975fa83515c9013c654ef5bb74d9ca8f27d..8725e0c440465e0b005fe3fc663e90c6f3ccbeb5 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -26,7 +26,8 @@ import { ITestingProgressUiService, TestingProgressUiService } from 'vs/workbenc import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { testingConfiguation } from 'vs/workbench/contrib/testing/common/configuration'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; -import { TestIdPath, ITestIdWithSrc, identifyTest } from 'vs/workbench/contrib/testing/common/testCollection'; +import { identifyTest, ITestIdWithSrc, TestIdPath, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService, TestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { ITestingAutoRun, TestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun'; import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; @@ -37,15 +38,17 @@ import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { allTestActions, runTestsByPath } from './testExplorerActions'; +import './testingConfigurationUi'; -registerSingleton(ITestService, TestService); -registerSingleton(ITestResultStorage, TestResultStorage); -registerSingleton(ITestResultService, TestResultService); -registerSingleton(ITestExplorerFilterState, TestExplorerFilterState); +registerSingleton(ITestService, TestService, true); +registerSingleton(ITestResultStorage, TestResultStorage, true); +registerSingleton(ITestConfigurationService, TestConfigurationService, true); +registerSingleton(ITestResultService, TestResultService, true); +registerSingleton(ITestExplorerFilterState, TestExplorerFilterState, true); registerSingleton(ITestingAutoRun, TestingAutoRun, true); registerSingleton(ITestingOutputTerminalService, TestingOutputTerminalService, true); -registerSingleton(ITestingPeekOpener, TestingPeekOpener); -registerSingleton(ITestingProgressUiService, TestingProgressUiService); +registerSingleton(ITestingPeekOpener, TestingPeekOpener, true); +registerSingleton(ITestingProgressUiService, TestingProgressUiService, true); const viewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Testing.ViewletId, @@ -110,7 +113,7 @@ CommandsRegistry.registerCommand({ id: 'vscode.runTests', handler: async (accessor: ServicesAccessor, tests: ITestIdWithSrc[]) => { const testService = accessor.get(ITestService); - testService.runTests({ debug: false, tests }); + testService.runTests({ group: TestRunConfigurationBitset.Run, tests }); } }); @@ -118,7 +121,7 @@ CommandsRegistry.registerCommand({ id: 'vscode.debugTests', handler: async (accessor: ServicesAccessor, tests: ITestIdWithSrc[]) => { const testService = accessor.get(ITestService); - testService.runTests({ debug: true, tests }); + testService.runTests({ group: TestRunConfigurationBitset.Debug, tests }); } }); @@ -142,16 +145,13 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: 'vscode.runTestsByPath', - handler: async (accessor: ServicesAccessor, debug: boolean, ...pathToTests: TestIdPath[]) => { + handler: async (accessor: ServicesAccessor, group: TestRunConfigurationBitset, ...pathToTests: TestIdPath[]) => { const testService = accessor.get(ITestService); await runTestsByPath( accessor.get(ITestService).collection, accessor.get(IProgressService), pathToTests, - tests => testService.runTests({ - debug: false, - tests: tests.map(identifyTest), - }), + tests => testService.runTests({ group, tests: tests.map(identifyTest) }), ); } }); diff --git a/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts b/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts new file mode 100644 index 0000000000000000000000000000000000000000..f39adbdf5e46afeb0b833b98c76d0149ac443614 --- /dev/null +++ b/src/vs/workbench/contrib/testing/browser/testingConfigurationUi.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { groupBy } from 'vs/base/common/arrays'; +import { isDefined } from 'vs/base/common/types'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { localize } from 'vs/nls'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { QuickPickInput, IQuickPickItem, IQuickInputService, IQuickPickItemButtonEvent } from 'vs/platform/quickinput/common/quickInput'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { testingUpdateConfiguration } from 'vs/workbench/contrib/testing/browser/icons'; +import { testConfigurationGroupNames } from 'vs/workbench/contrib/testing/common/constants'; +import { ITestRunConfiguration, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; + +interface IConfigurationPickerOptions { + /** Placeholder text */ + placeholder?: string; + /** Show buttons to trigger configuration */ + showConfigureButtons?: boolean; + /** Only show configurations from this controller */ + onlyControllerId?: string; + /** Only show this group */ + onlyGroup?: TestRunConfigurationBitset; + /** Only show items which are configurable */ + onlyConfigurable?: boolean; +} + +function buildPicker(accessor: ServicesAccessor, { + onlyGroup, + showConfigureButtons, + onlyControllerId, + onlyConfigurable, + placeholder = localize('testConfigurationUi.pick', 'Pick a test configuration to use'), +}: IConfigurationPickerOptions) { + const configService = accessor.get(ITestConfigurationService); + const items: QuickPickInput[] = []; + const pushItems = (allConfigs: ITestRunConfiguration[], description?: string) => { + for (const configs of groupBy(allConfigs, (a, b) => a.group - b.group)) { + let addedHeader = false; + if (onlyGroup) { + if (configs[0].group !== onlyGroup) { + continue; + } + + addedHeader = true; // showing one group, no need for label + } + + for (const config of configs) { + if (onlyConfigurable && !config.hasConfigurationHandler) { + continue; + } + + if (!addedHeader) { + items.push({ type: 'separator', label: testConfigurationGroupNames[configs[0].group] }); + addedHeader = true; + } + + items.push(({ + type: 'item', + config, + label: config.label, + description, + alwaysShow: true, + buttons: config.hasConfigurationHandler && showConfigureButtons + ? [{ + iconClass: ThemeIcon.asClassName(testingUpdateConfiguration), + tooltip: localize('updateTestConfiguration', 'Update Test Configuration') + }] : [] + })); + } + } + }; + + if (onlyControllerId !== undefined) { + const lookup = configService.getControllerConfigurations(onlyControllerId); + if (!lookup) { + return; + } + + pushItems(lookup.configs); + } else { + for (const { configs, controller } of configService.all()) { + pushItems(configs, controller.label.value); + } + } + + const quickpick = accessor.get(IQuickInputService).createQuickPick(); + quickpick.items = items; + quickpick.placeholder = placeholder; + return quickpick; +} + +const triggerButtonHandler = (service: ITestConfigurationService, resolve: (arg: undefined) => void) => + (evt: IQuickPickItemButtonEvent) => { + const config = (evt.item as { config?: ITestRunConfiguration }).config; + if (config) { + service.configure(config.controllerId, config.profileId); + resolve(undefined); + } + }; + +CommandsRegistry.registerCommand({ + id: 'vscode.pickMultipleTestProfiles', + handler: async (accessor: ServicesAccessor, options: IConfigurationPickerOptions & { + selected?: ITestRunConfiguration[], + }) => { + const configService = accessor.get(ITestConfigurationService); + const quickpick = buildPicker(accessor, options); + if (!quickpick) { + return; + } + + quickpick.canSelectMany = true; + if (options.selected) { + quickpick.selectedItems = quickpick.items + .filter((i): i is IQuickPickItem & { config: ITestRunConfiguration } => i.type === 'item') + .filter(i => options.selected!.some(s => s.controllerId === i.config.controllerId && s.profileId === i.config.profileId)); + } + + const pick = await new Promise(resolve => { + quickpick.onDidAccept(() => { + const selected = quickpick.selectedItems as readonly { config?: ITestRunConfiguration }[]; + resolve(selected.map(s => s.config).filter(isDefined)); + }); + quickpick.onDidHide(() => resolve(undefined)); + quickpick.onDidTriggerItemButton(triggerButtonHandler(configService, resolve)); + quickpick.show(); + }); + + quickpick.dispose(); + return pick; + } +}); + +CommandsRegistry.registerCommand({ + id: 'vscode.pickTestProfile', + handler: async (accessor: ServicesAccessor, options: IConfigurationPickerOptions) => { + const configService = accessor.get(ITestConfigurationService); + const quickpick = buildPicker(accessor, options); + if (!quickpick) { + return; + } + + const pick = await new Promise(resolve => { + quickpick.onDidAccept(() => resolve((quickpick.selectedItems[0] as { config?: ITestRunConfiguration })?.config)); + quickpick.onDidHide(() => resolve(undefined)); + quickpick.onDidTriggerItemButton(triggerButtonHandler(configService, resolve)); + quickpick.show(); + }); + + quickpick.dispose(); + return pick; + } +}); + diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 6628cfdddc1241784a7d8ebb266bef6a53530833..1b3c9bada7147d9ccbd98f7f4c8352c9afacfc85 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -30,7 +30,8 @@ import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browse import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme'; import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration'; import { labelForTestInState } from 'vs/workbench/contrib/testing/common/constants'; -import { identifyTest, IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; +import { identifyTest, IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunConfiguration, TestResultItem, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { maxPriority } from 'vs/workbench/contrib/testing/common/testingStates'; import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; @@ -121,7 +122,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio } })); - this._register(Event.any(this.results.onResultsChanged, this.testService.excludeTests.onDidChange)(() => { + this._register(Event.any(this.results.onResultsChanged, this.testService.excluded.onTestExclusionsChanged)(() => { if (this.currentUri) { this.setDecorations(this.currentUri); } @@ -313,6 +314,7 @@ abstract class RunTestDecoration extends Disposable { @IContextMenuService protected readonly contextMenuService: IContextMenuService, @ICommandService protected readonly commandService: ICommandService, @IConfigurationService protected readonly configurationService: IConfigurationService, + @ITestConfigurationService protected readonly testConfigurationService: ITestConfigurationService, ) { super(); editorDecoration.options.glyphMarginHoverMessage = new MarkdownString().appendText(this.getGutterLabel()); @@ -402,20 +404,39 @@ abstract class RunTestDecoration extends Disposable { */ protected getTestContextMenuActions(collection: IMainThreadTestCollection, test: InternalTestItem) { const testActions: IAction[] = []; - if (test.item.runnable) { + const capabilities = this.testConfigurationService.controllerCapabilities(test.controllerId); + if (capabilities & TestRunConfigurationBitset.Run) { testActions.push(new Action('testing.gutter.run', localize('run test', 'Run Test'), undefined, undefined, () => this.testService.runTests({ - debug: false, + group: TestRunConfigurationBitset.Run, tests: [identifyTest(test)], }))); } - if (test.item.debuggable) { + if (capabilities & TestRunConfigurationBitset.Debug) { testActions.push(new Action('testing.gutter.debug', localize('debug test', 'Debug Test'), undefined, undefined, () => this.testService.runTests({ - debug: true, + group: TestRunConfigurationBitset.Debug, tests: [identifyTest(test)], }))); } + if (capabilities & TestRunConfigurationBitset.HasNonDefaultConfig) { + testActions.push(new Action('testing.runUsing', localize('testing.runUsing', 'Execute Using Profile...'), undefined, undefined, async () => { + const config: ITestRunConfiguration | undefined = await this.commandService.executeCommand('vscode.pickTestProfile', { onlyControllerId: test.controllerId }); + if (!config) { + return; + } + + this.testService.runResolvedTests({ + targets: [{ + profileGroup: config.group, + profileId: config.profileId, + controllerId: config.controllerId, + testIds: [test.item.extId] + }] + }); + })); + } + testActions.push(new Action('testing.gutter.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined, async () => { const path = [test]; while (true) { @@ -446,8 +467,9 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio @ICommandService commandService: ICommandService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, + @ITestConfigurationService testConfigurationService: ITestConfigurationService, ) { - super(createRunTestDecoration(tests.map(t => t.test), tests.map(t => t.resultItem)), editor, testService, contextMenuService, commandService, configurationService); + super(createRunTestDecoration(tests.map(t => t.test), tests.map(t => t.resultItem)), editor, testService, contextMenuService, commandService, configurationService, testConfigurationService); } public override merge(test: IncrementalTestCollectionItem, resultItem: TestResultItem | undefined): RunTestDecoration { @@ -458,11 +480,11 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio protected override getContextMenuActions() { const allActions: IAction[] = []; - if (this.tests.some(({ test }) => test.item.runnable)) { + if (this.tests.some(({ test }) => this.testConfigurationService.controllerCapabilities(test.controllerId) & TestRunConfigurationBitset.Run)) { allActions.push(new Action('testing.gutter.runAll', localize('run all test', 'Run All Tests'), undefined, undefined, () => this.defaultRun())); } - if (this.tests.some(({ test }) => test.item.debuggable)) { + if (this.tests.some(({ test }) => this.testConfigurationService.controllerCapabilities(test.controllerId) & TestRunConfigurationBitset.Debug)) { allActions.push(new Action('testing.gutter.debugAll', localize('debug all test', 'Debug All Tests'), undefined, undefined, () => this.defaultDebug())); } @@ -474,19 +496,15 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio protected override defaultRun() { return this.testService.runTests({ - tests: this.tests - .filter(({ test }) => test.item.runnable) - .map(({ test }) => identifyTest(test)), - debug: false, + tests: this.tests.map(({ test }) => identifyTest(test)), + group: TestRunConfigurationBitset.Run, }); } protected override defaultDebug() { return this.testService.runTests({ - tests: this.tests - .filter(({ test }) => test.item.debuggable) - .map(({ test }) => identifyTest(test)), - debug: true, + tests: this.tests.map(({ test }) => identifyTest(test)), + group: TestRunConfigurationBitset.Run, }); } } @@ -500,15 +518,16 @@ class RunSingleTestDecoration extends RunTestDecoration implements ITestDecorati @ICommandService commandService: ICommandService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, + @ITestConfigurationService testConfigurationService: ITestConfigurationService, ) { - super(createRunTestDecoration([test], [resultItem]), editor, testService, contextMenuService, commandService, configurationService); + super(createRunTestDecoration([test], [resultItem]), editor, testService, contextMenuService, commandService, configurationService, testConfigurationService); } public override merge(test: IncrementalTestCollectionItem, resultItem: TestResultItem | undefined): RunTestDecoration { return new MultiRunTestDecoration([ { test: this.test, resultItem: this.resultItem }, { test, resultItem }, - ], this.editor, this.testService, this.commandService, this.contextMenuService, this.configurationService); + ], this.editor, this.testService, this.commandService, this.contextMenuService, this.configurationService, this.testConfigurationService); } protected override getContextMenuActions(e: IEditorMouseEvent) { @@ -516,24 +535,16 @@ class RunSingleTestDecoration extends RunTestDecoration implements ITestDecorati } protected override defaultRun() { - if (!this.test.item.runnable) { - return; - } - return this.testService.runTests({ tests: [identifyTest(this.test)], - debug: false, + group: TestRunConfigurationBitset.Run, }); } protected override defaultDebug() { - if (!this.test.item.debuggable) { - return; - } - return this.testService.runTests({ tests: [identifyTest(this.test)], - debug: true, + group: TestRunConfigurationBitset.Debug, }); } } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index a4f8d4e1641ff579083ee9c6791f687ebdf7aeb6..41ff97d51487d4495353f009c0b0a4d1a824a7de 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -246,10 +246,10 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { { checked: false, class: undefined, - enabled: this.testService.excludeTests.value.size > 0, + enabled: this.testService.excluded.hasAny, id: 'removeExcluded', label: localize('testing.filters.removeTestExclusions', "Unhide All Tests"), - run: async () => this.testService.clearExcludedTests(), + run: async () => this.testService.excluded.clear(), tooltip: '', dispose: () => null }, diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index f83e90bab2ddc4af29f815a56a13724412f8ff16..76847cffa2180453e629016bdb5e94c892933780 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -17,7 +17,6 @@ import { Color, RGBA } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; import { splitGlobAware } from 'vs/base/common/glob'; -import { Iterable } from 'vs/base/common/iterator'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, dispose, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isDefined } from 'vs/base/common/types'; @@ -25,8 +24,10 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/testing'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { localize } from 'vs/nls'; +import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -49,19 +50,20 @@ import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/comm import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation'; import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName'; import { ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; -import { testingHiddenIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons'; +import * as icons from 'vs/workbench/contrib/testing/browser/icons'; import { ITestExplorerFilterState, TestExplorerFilterState, TestingExplorerFilter } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter'; import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService'; import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration'; import { labelForTestInState, TestExplorerStateFilter, TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants'; -import { identifyTest, TestIdPath, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection'; +import { identifyTest, ITestRunConfiguration, TestIdPath, TestItemExpandState, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { capabilityContextKeys, ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { cmpPriority, isFailedState, isStateWithResult } from 'vs/workbench/contrib/testing/common/testingStates'; import { getPathForTestInResult, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService, testCollectionIsEmpty } from 'vs/workbench/contrib/testing/common/testService'; -import { GoToTest } from './testExplorerActions'; +import { ConfigureTestProfilesAction, DebugAllAction, GoToTest, RunAllAction, SelectDefaultTestProfiles } from './testExplorerActions'; export class TestingExplorerView extends ViewPane { public viewModel!: TestingExplorerViewModel; @@ -82,9 +84,11 @@ export class TestingExplorerView extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, - @ITestService testService: ITestService, + @ITestService private readonly testService: ITestService, @ITelemetryService telemetryService: ITelemetryService, @ITestingProgressUiService private readonly testProgressService: ITestingProgressUiService, + @ITestConfigurationService private readonly testConfigurationService: ITestConfigurationService, + @ICommandService private readonly commandService: ICommandService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.location.set(viewDescriptorService.getViewLocationById(Testing.ExplorerViewId) ?? ViewContainerLocation.Sidebar); @@ -99,6 +103,8 @@ export class TestingExplorerView extends ViewPane { this._register(testService.collection.onBusyProvidersChange(busy => { this.updateDiscoveryProgress(busy); })); + + this._register(testConfigurationService.onDidChange(() => this.updateActions())); } /** @@ -153,15 +159,88 @@ export class TestingExplorerView extends ViewPane { } } - /** - * @override - */ + /** @override */ public override getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === Testing.FilterActionId) { - return this.instantiationService.createInstance(TestingExplorerFilter, action); + switch (action.id) { + case Testing.FilterActionId: + return this.instantiationService.createInstance(TestingExplorerFilter, action); + case RunAllAction.ID: + return this.getRunGroupDropdown(TestRunConfigurationBitset.Run, action); + case DebugAllAction.ID: + return this.getRunGroupDropdown(TestRunConfigurationBitset.Debug, action); + default: + return super.getActionViewItem(action); + } + } + + /** @inheritdoc */ + private getTestConfigGroupActions(group: TestRunConfigurationBitset) { + const profileActions: IAction[] = []; + + let participatingGroups = 0; + let hasConfigurable = false; + const defaults = this.testConfigurationService.getGroupDefaultConfigurations(group); + for (const { configs, controller } of this.testConfigurationService.all()) { + let hasAdded = false; + + for (const config of configs) { + if (config.group !== group) { + continue; + } + + if (!hasAdded) { + hasAdded = true; + participatingGroups++; + profileActions.push(new Action(`${controller.id}.$root`, controller.label.value, undefined, false)); + } + + hasConfigurable = hasConfigurable || config.hasConfigurationHandler; + profileActions.push(new Action( + `${controller.id}.${config.profileId}`, + defaults.includes(config) ? localize('defaultTestProfile', '{0} (Default)', config.label) : config.label, + undefined, + undefined, + () => this.testService.runResolvedTests({ + targets: [{ + profileGroup: config.group, + profileId: config.profileId, + controllerId: config.controllerId, + testIds: this.getSelectedOrVisibleItems() + .filter(i => i.controllerId === config.controllerId) + .map(i => i.item.extId), + }] + }), + )); + } + } + + // If there's only one group, don't add a heading for it in the dropdown. + if (participatingGroups === 1) { + profileActions.shift(); + } + + let postActions: IAction[] = []; + if (profileActions.length > 1) { + postActions.push(new Action( + 'selectDefaultTestConfigurations', + localize('selectDefaultConfigs', 'Select Default Profile'), + undefined, + undefined, + () => this.commandService.executeCommand(SelectDefaultTestProfiles.ID, group), + )); } - return super.getActionViewItem(action); + if (hasConfigurable) { + postActions.push(new Action( + 'configureTestProfiles', + localize('configureTestProfiles', 'Configure Test Profiles'), + undefined, + undefined, + () => this.commandService.executeCommand(ConfigureTestProfilesAction.ID, group), + )); + } + + return Separator.join(profileActions, postActions); } /** @@ -171,6 +250,38 @@ export class TestingExplorerView extends ViewPane { super.saveState(); } + /** + * If items in the tree are selected, returns them. Otherwise, returns + * visible tests. + */ + private getSelectedOrVisibleItems() { + return [...this.testService.collection.rootItems]; // todo + } + + private getRunGroupDropdown(group: TestRunConfigurationBitset, defaultAction: IAction) { + const dropdownActions = this.getTestConfigGroupActions(group); + if (dropdownActions.length < 2) { + return super.getActionViewItem(defaultAction); + } + + const primaryAction = this.instantiationService.createInstance(MenuItemAction, { + id: defaultAction.id, + title: defaultAction.label, + icon: group === TestRunConfigurationBitset.Run + ? icons.testingRunAllIcon + : icons.testingDebugAllIcon, + }, undefined, undefined); + + const dropdownAction = new Action('selectRunConfig', 'Select Configuration...', 'codicon-chevron-down', true); + + return this.instantiationService.createInstance( + DropdownWithPrimaryActionViewItem, + primaryAction, dropdownAction, dropdownActions, + '', + this.contextMenuService, + ); + } + private createFilterActionBar() { const bar = new ActionBar(this.treeHeader, { actionViewItemProvider: action => this.getActionViewItem(action), @@ -278,6 +389,7 @@ export class TestingExplorerViewModel extends Disposable { @IContextKeyService private readonly contextKeyService: IContextKeyService, @ITestResultService private readonly testResults: ITestResultService, @ITestingPeekOpener private readonly peekOpener: ITestingPeekOpener, + @ITestConfigurationService private readonly testConfigurationService: ITestConfigurationService, ) { super(); @@ -321,7 +433,7 @@ export class TestingExplorerViewModel extends Disposable { filterState.text.onDidChange, filterState.stateFilter.onDidChange, filterState.showExcludedTests.onDidChange, - testService.excludeTests.onDidChange, + testService.excluded.onTestExclusionsChanged, )(this.tree.refilter, this.tree)); this._register(this.tree); @@ -396,6 +508,10 @@ export class TestingExplorerViewModel extends Disposable { } } })); + + this._register(this.testConfigurationService.onDidChange(() => { + this.tree.rerender(); + })); } /** @@ -446,7 +562,7 @@ export class TestingExplorerViewModel extends Disposable { // If the node or any of its children are excluded, flip on the 'show // excluded tests' checkbox automatically. for (let n: TestItemTreeElement | null = element; n instanceof TestItemTreeElement; n = n.parent) { - if (n.test && this.testService.excludeTests.value.has(n.test.item.extId)) { + if (n.test && this.testService.excluded.contains(identifyTest(n.test))) { this.filterState.showExcludedTests.value = true; break; } @@ -499,7 +615,7 @@ export class TestingExplorerViewModel extends Disposable { return; } - const actions = getActionableElementActions(this.contextKeyService, this.menuService, this.testService, element); + const actions = getActionableElementActions(this.contextKeyService, this.menuService, this.testService, this.testConfigurationService, element); this.contextMenuService.showContextMenu({ getAnchor: () => evt.anchor, getActions: () => [ @@ -525,12 +641,11 @@ export class TestingExplorerViewModel extends Disposable { } const toRun = targeted - .filter((e): e is TestItemTreeElement => e instanceof TestItemTreeElement) - .filter(e => e.test.item.runnable); + .filter((e): e is TestItemTreeElement => e instanceof TestItemTreeElement); if (toRun.length) { this.testService.runTests({ - debug: false, + group: TestRunConfigurationBitset.Run, tests: toRun.map(t => identifyTest(t.test)), }); } @@ -641,7 +756,7 @@ class TestsFilter implements ITreeFilter { if ( element.test && !this.state.showExcludedTests.value - && this.testService.excludeTests.value.has(element.test.item.extId) + && this.testService.excluded.contains(identifyTest(element.test)) ) { return TreeVisibility.Hidden; } @@ -883,10 +998,11 @@ abstract class ActionableItemTemplateData extends constructor( protected readonly labels: ResourceLabels, private readonly actionRunner: TestExplorerActionRunner, - private readonly menuService: IMenuService, - protected readonly testService: ITestService, - private readonly contextKeyService: IContextKeyService, - private readonly instantiationService: IInstantiationService, + @IMenuService private readonly menuService: IMenuService, + @ITestService protected readonly testService: ITestService, + @ITestConfigurationService protected readonly configurations: ITestConfigurationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); } @@ -906,7 +1022,7 @@ abstract class ActionableItemTemplateData extends const name = dom.append(wrapper, dom.$('.name')); const label = this.labels.create(name, { supportHighlights: true }); - dom.append(wrapper, dom.$(ThemeIcon.asCSSSelector(testingHiddenIcon))); + dom.append(wrapper, dom.$(ThemeIcon.asCSSSelector(icons.testingHiddenIcon))); const actionBar = new ActionBar(wrapper, { actionRunner: this.actionRunner, actionViewItemProvider: action => @@ -942,7 +1058,7 @@ abstract class ActionableItemTemplateData extends } private fillActionBar(element: T, data: IActionableElementTemplateData) { - const actions = getActionableElementActions(this.contextKeyService, this.menuService, this.testService, element); + const actions = getActionableElementActions(this.contextKeyService, this.menuService, this.testService, this.configurations, element); data.elementDisposable.push(actions); data.actionBar.clear(); data.actionBar.context = element; @@ -953,17 +1069,6 @@ abstract class ActionableItemTemplateData extends class TestItemRenderer extends ActionableItemTemplateData { public static readonly ID = 'testItem'; - constructor( - labels: ResourceLabels, - actionRunner: TestExplorerActionRunner, - @IMenuService menuService: IMenuService, - @ITestService testService: ITestService, - @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(labels, actionRunner, menuService, testService, contextKeyService, instantiationService); - } - /** * @inheritdoc */ @@ -981,10 +1086,10 @@ class TestItemRenderer extends ActionableItemTemplateData { const options: IResourceLabelOptions = {}; data.label.setResource(label, options); - const testHidden = this.testService.excludeTests.value.has(node.element.test.item.extId); + const testHidden = this.testService.excluded.contains(identifyTest(node.element.test)); data.wrapper.classList.toggle('test-is-hidden', testHidden); - const icon = testingStatesToIcons.get( + const icon = icons.testingStatesToIcons.get( node.element.test.expand === TestItemExpandState.BusyExpanding || node.element.test.item.busy ? TestResultState.Running : node.element.state); @@ -1025,6 +1130,7 @@ const getActionableElementActions = ( contextKeyService: IContextKeyService, menuService: IMenuService, testService: ITestService, + configurations: ITestConfigurationService, element: TestItemTreeElement, ) => { const test = element instanceof TestItemTreeElement ? element.test : undefined; @@ -1032,9 +1138,8 @@ const getActionableElementActions = ( ['view', Testing.ExplorerViewId], [TestingContextKeys.testItemExtId.key, test?.item.extId], [TestingContextKeys.testItemHasUri.key, !!test?.item.uri], - [TestingContextKeys.testItemIsHidden.key, !!test && testService.excludeTests.value.has(test.item.extId)], - [TestingContextKeys.hasDebuggableTests.key, !Iterable.isEmpty(element.debuggable)], - [TestingContextKeys.hasRunnableTests.key, !Iterable.isEmpty(element.runnable)], + [TestingContextKeys.testItemIsHidden.key, !!test && testService.excluded.contains(identifyTest(test))], + ...(test ? capabilityContextKeys(configurations.controllerCapabilities(test.controllerId)) : []), ]); const menu = menuService.createMenu(MenuId.TestItem, contextOverlay); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 7fa1e04b51f44e79bc87813938d868dced27ab26..8b582ce0c932677d9dba57a57bdf7abf24de38d3 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -58,7 +58,8 @@ import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/brow import { testingPeekBorder } from 'vs/workbench/contrib/testing/browser/theme'; import { AutoOpenPeekViewWhen, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; -import { IRichLocation, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; +import { IRichLocation, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestResultItem, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { capabilityContextKeys, ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; @@ -186,7 +187,7 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener return; } - if (evt.result.isAutoRun && !getTestingConfiguration(this.configuration, TestingConfigKeys.AutoOpenPeekViewDuringAutoRun)) { + if (evt.result.request.isAutoRun && !getTestingConfiguration(this.configuration, TestingConfigKeys.AutoOpenPeekViewDuringAutoRun)) { return; } @@ -1269,17 +1270,18 @@ class TreeActionsProvider { @ITestingOutputTerminalService private readonly testTerminalService: ITestingOutputTerminalService, @IMenuService private readonly menuService: IMenuService, @ICommandService private readonly commandService: ICommandService, + @ITestConfigurationService private readonly testConfigurationService: ITestConfigurationService, ) { } public provideActionBar(element: ITreeElement) { const test = element instanceof TestCaseElement ? element.test : undefined; + const capabilities = test ? this.testConfigurationService.controllerCapabilities(test.controllerId) : 0; const contextOverlay = this.contextKeyService.createOverlay([ ['peek', Testing.OutputPeekContributionId], [TestingContextKeys.peekItemType.key, element.type], [TestingContextKeys.testItemExtId.key, test?.item.extId], [TestingContextKeys.testItemHasUri.key, !!test?.item.uri], - [TestingContextKeys.hasDebuggableTests.key, test?.item.debuggable], - [TestingContextKeys.hasRunnableTests.key, test?.item.debuggable], + ...(test ? capabilityContextKeys(capabilities) : []) ]); const menu = this.menuService.createMenu(MenuId.TestPeekElement, contextOverlay); @@ -1304,7 +1306,7 @@ class TreeActionsProvider { () => this.commandService.executeCommand('testing.reRunLastRun', element.value.id), )); - if (Iterable.some(element.value.tests, t => t.item.debuggable)) { + if (capabilities & TestRunConfigurationBitset.Debug) { primary.push(new Action( 'testing.outputPeek.debugLastRun', localize('testing.debugLastRun', "Debug Test Run"), @@ -1324,7 +1326,7 @@ class TreeActionsProvider { () => this.commandService.executeCommand('vscode.revealTestInExplorer', element.path), )); - if (element.test.item.runnable) { + if (capabilities & TestRunConfigurationBitset.Run) { primary.push(new Action( 'testing.outputPeek.runTest', localize('run test', 'Run Test'), @@ -1334,7 +1336,7 @@ class TreeActionsProvider { )); } - if (element.test.item.debuggable) { + if (capabilities & TestRunConfigurationBitset.Coverage) { primary.push(new Action( 'testing.outputPeek.debugTest', localize('debug test', 'Debug Test'), diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index 509cc0b9ea020f3276122a0b025b3606f11086f3..ab1d44c9b9511538717ce520b1a8b51cf512f3bf 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -5,6 +5,7 @@ import { localize } from 'vs/nls'; import { TestResultState } from 'vs/workbench/api/common/extHostTypes'; +import { TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; export const enum Testing { // marked as "extension" so that any existing test extensions are assigned to it. @@ -45,3 +46,9 @@ export const labelForTestInState = (label: string, state: TestResultState) => lo key: 'testing.treeElementLabel', comment: ['label then the unit tests state, for example "Addition Tests (Running)"'], }, '{0} ({1})', label, testStateNames[state]); + +export const testConfigurationGroupNames: { [K in TestRunConfigurationBitset]: string } = { + [TestRunConfigurationBitset.Debug]: localize('testGroup.debug', 'Debug'), + [TestRunConfigurationBitset.Run]: localize('testGroup.run', 'Run'), + [TestRunConfigurationBitset.Coverage]: localize('testGroup.coverage', 'Coverage'), +}; diff --git a/src/vs/workbench/contrib/testing/common/observableValue.ts b/src/vs/workbench/contrib/testing/common/observableValue.ts index a736966b46d092f9337f5f489a8666faf372a1d1..bc14ca7fb0fae7eb70c54b0a0c465077e5671b49 100644 --- a/src/vs/workbench/contrib/testing/common/observableValue.ts +++ b/src/vs/workbench/contrib/testing/common/observableValue.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; export interface IObservableValue { @@ -16,8 +17,8 @@ export const staticObservableValue = (value: T): IObservableValue => ({ value, }); -export class MutableObservableValue implements IObservableValue { - private readonly changeEmitter = new Emitter(); +export class MutableObservableValue extends Disposable implements IObservableValue { + private readonly changeEmitter = this._register(new Emitter()); public readonly onDidChange = this.changeEmitter.event; @@ -38,5 +39,7 @@ export class MutableObservableValue implements IObservableValue { return o; } - constructor(private _value: T) { } + constructor(private _value: T) { + super(); + } } diff --git a/src/vs/workbench/contrib/testing/common/ownedTestCollection.ts b/src/vs/workbench/contrib/testing/common/ownedTestCollection.ts index b8b294bb1f7bfa33170496dab172052f076c8f1c..8d62e8ab4cb74bd4ee8b34b68f010382baba01ff 100644 --- a/src/vs/workbench/contrib/testing/common/ownedTestCollection.ts +++ b/src/vs/workbench/contrib/testing/common/ownedTestCollection.ts @@ -6,13 +6,11 @@ import { Barrier, isThenable, RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { Iterable } from 'vs/base/common/iterator'; import { Disposable } from 'vs/base/common/lifecycle'; import { assertNever } from 'vs/base/common/types'; -import { ExtHostTestItemEvent, ExtHostTestItemEventType, getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; +import { diffTestItems, ExtHostTestItemEvent, ExtHostTestItemEventOp, getPrivateApiFor, TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; -import { TestItemImpl } from 'vs/workbench/api/common/extHostTypes'; -import { applyTestItemUpdate, InternalTestItem, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection'; +import { applyTestItemUpdate, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection'; type TestItemRaw = Convert.TestItem.Raw; @@ -23,7 +21,9 @@ export interface IHierarchyProvider { /** * @private */ -export interface OwnedCollectionTestItem extends InternalTestItem { +export interface OwnedCollectionTestItem { + expand: TestItemExpandState; + parent: string | null; actual: TestItemImpl; /** * Number of levels of items below this one that are expanded. May be infinite. @@ -52,7 +52,7 @@ export const enum TestPosition { * for test trees. Internally it indexes tests by their extension ID in * a map. */ -export class TestTree { +export class TestTree { private readonly map = new Map(); private readonly _roots = new Set(); public readonly roots: ReadonlySet = this._roots; @@ -69,11 +69,11 @@ export class TestTree { * @throws if a duplicate item is inserted */ public add(test: T) { - if (this.map.has(test.item.extId)) { - throw new Error(`Attempted to insert a duplicate test item ID ${test.item.extId}`); + if (this.map.has(test.actual.id)) { + throw new Error(`Attempted to insert a duplicate test item ID ${test.actual.id}`); } - this.map.set(test.item.extId, test); + this.map.set(test.actual.id, test); if (!test.parent) { this._roots.add(test); } @@ -150,12 +150,11 @@ export class TestTree { * @private */ export class SingleUseTestCollection extends Disposable { - protected readonly testItemToInternal = new Map(); private readonly debounceSendDiff = this._register(new RunOnceScheduler(() => this.flushDiff(), 200)); private readonly diffOpEmitter = this._register(new Emitter()); private _resolveHandler?: (item: TestItemRaw) => Promise | void; - public readonly root = new TestItemImpl(`${this.controllerId}Root`, this.controllerId, undefined, undefined, undefined); + public readonly root = new TestItemImpl(`${this.controllerId}Root`, this.controllerId, undefined); public readonly tree = new TestTree(); protected diff: TestsDiff = []; @@ -163,7 +162,7 @@ export class SingleUseTestCollection extends Disposable { private readonly controllerId: string, ) { super(); - this.addItemInner(this.root, null); + this.upsertItem(this.root, null); } /** @@ -171,7 +170,7 @@ export class SingleUseTestCollection extends Disposable { */ public set resolveHandler(handler: undefined | ((item: TestItemRaw) => void)) { this._resolveHandler = handler; - for (const test of this.testItemToInternal.values()) { + for (const test of this.tree) { this.updateExpandability(test); } } @@ -181,18 +180,6 @@ export class SingleUseTestCollection extends Disposable { */ public readonly onDidGenerateDiff = this.diffOpEmitter.event; - public get roots() { - return Iterable.filter(this.testItemToInternal.values(), t => t.parent === null); - } - - /** - * Gets test information by its reference, if it was defined and still exists - * in this extension host. - */ - public getTestByReference(item: TestItemRaw) { - return this.testItemToInternal.get(item); - } - /** * Gets a diff of all changes that have been made, and clears the diff queue. */ @@ -257,8 +244,8 @@ export class SingleUseTestCollection extends Disposable { } public override dispose() { - for (const item of this.testItemToInternal.values()) { - getPrivateApiFor(item.actual).bus.dispose(); + for (const item of this.tree) { + getPrivateApiFor(item.actual).listener = undefined; } this.diff = []; @@ -268,21 +255,27 @@ export class SingleUseTestCollection extends Disposable { private onTestItemEvent(internal: OwnedCollectionTestItem, evt: ExtHostTestItemEvent) { const extId = internal?.actual.id; - switch (evt[0]) { - case ExtHostTestItemEventType.Invalidated: + switch (evt.op) { + case ExtHostTestItemEventOp.Invalidated: this.pushDiff([TestDiffOpType.Retire, extId]); break; - case ExtHostTestItemEventType.Disposed: - this.removeItem(internal); + case ExtHostTestItemEventOp.RemoveChild: + this.removeItem(evt.id); break; - case ExtHostTestItemEventType.NewChild: - this.addItemInner(evt[1], internal); + case ExtHostTestItemEventOp.Upsert: + this.upsertItem(evt.item, internal); break; - case ExtHostTestItemEventType.SetProp: - const [_, key, value] = evt; + case ExtHostTestItemEventOp.Bulk: + for (const op of evt.ops) { + this.onTestItemEvent(internal, op); + } + break; + + case ExtHostTestItemEventOp.SetProp: + const { key, value } = evt; switch (key) { case 'canResolveChildren': this.updateExpandability(internal); @@ -299,54 +292,88 @@ export class SingleUseTestCollection extends Disposable { } break; default: - assertNever(evt[0]); + assertNever(evt); } } - private addItemInner(actual: TestItemRaw, parent: OwnedCollectionTestItem | null) { + private upsertItem(actual: TestItemRaw, parent: OwnedCollectionTestItem | null) { if (!(actual instanceof TestItemImpl)) { throw new Error(`TestItems provided to the VS Code API must extend \`vscode.TestItem\`, but ${actual.id} did not`); } - if (this.testItemToInternal.has(actual)) { - throw new Error(`Attempted to add a single TestItem ${actual.id} multiple times to the tree`); + + // If the item already exists under a different parent, remove it. + let internal = this.tree.get(actual.id); + if (internal && internal.parent !== parent?.actual.id) { + (internal.actual.parent ?? this.root).children.remove(actual.id); + internal = undefined; + } + + // Case 1: a brand new item + if (!internal) { + const parentId = parent ? parent.actual.id : null; + // always expand root node to know if there are tests (and whether to show the welcome view) + const pExpandLvls = parent ? parent.expandLevels : 1; + internal = { + actual, + parent: parentId, + expandLevels: pExpandLvls /* intentionally undefined or 0 */ ? pExpandLvls - 1 : undefined, + expand: TestItemExpandState.NotExpandable, // updated by `connectItemAndChildren` + }; + + this.tree.add(internal); + this.pushDiff([ + TestDiffOpType.Add, + { parent: parentId, controllerId: this.controllerId, expand: internal.expand, item: Convert.TestItem.from(actual) }, + ]); + + this.connectItemAndChildren(actual, internal, parent); + return; + } + + // Case 2: re-insertion of an existing item, no-op + if (internal.actual === actual) { + this.connectItem(actual, internal, parent); // re-connect in case the parent changed + return; // no-op } - if (this.tree.has(actual.id)) { - throw new Error(`Attempted to insert a duplicate test item ID ${actual.id}`); + // Case 3: upsert of an existing item by ID, with a new instance + const oldChildren = internal.actual.children.all; + const oldActual = internal.actual; + const changedProps = diffTestItems(oldActual, actual); + getPrivateApiFor(oldActual).listener = undefined; + + internal.actual = actual; + internal.expand = TestItemExpandState.NotExpandable; // updated by `connectItemAndChildren` + for (const [key, value] of changedProps) { + this.onTestItemEvent(internal, { op: ExtHostTestItemEventOp.SetProp, key, value }); } - const parentId = parent ? parent.item.extId : null; - // always expand root node to know if there are tests (and whether to show the welcome view) - const pExpandLvls = parent ? parent.expandLevels : 1; - const internal: OwnedCollectionTestItem = { - actual, - parent: parentId, - item: Convert.TestItem.from(actual), - expandLevels: pExpandLvls /* intentionally undefined or 0 */ ? pExpandLvls - 1 : undefined, - expand: TestItemExpandState.NotExpandable, // updated by `updateExpandability` down below - controllerId: this.controllerId, - }; + this.connectItemAndChildren(actual, internal, parent); - this.tree.add(internal); - this.testItemToInternal.set(actual, internal); - this.pushDiff([ - TestDiffOpType.Add, - { parent: parentId, controllerId: this.controllerId, expand: internal.expand, item: internal.item }, - ]); + // Remove any children still referencing the old parent that aren't + // included in the new one. Note that children might have moved to a new + // parent, so the parent ID check is done. + for (const child of oldChildren) { + if (!actual.children.get(child.id) && this.tree.get(child.id)?.parent === actual.id) { + this.removeItem(child.id); + } + } + } + private connectItem(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | null) { const api = getPrivateApiFor(actual); - api.bus.event(this.onTestItemEvent.bind(this, internal)); - - // important that this comes after binding the event bus otherwise we - // might miss a synchronous discovery completion + api.parent = parent && parent.actual !== this.root ? parent.actual : undefined; + api.listener = evt => this.onTestItemEvent(internal, evt); this.updateExpandability(internal); + } + + private connectItemAndChildren(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | null) { + this.connectItem(actual, internal, parent); // Discover any existing children that might have already been added - for (const child of api.children.values()) { - if (!this.testItemToInternal.has(child)) { - this.addItemInner(child, internal); - } + for (const child of actual.children.all) { + this.upsertItem(child, internal); } } @@ -391,7 +418,7 @@ export class SingleUseTestCollection extends Disposable { return; } - const asyncChildren = [...internal.actual.children.values()] + const asyncChildren = internal.actual.children.all .map(c => this.expand(c.id, levels)) .filter(isThenable); @@ -443,19 +470,24 @@ export class SingleUseTestCollection extends Disposable { this.pushDiff([TestDiffOpType.Update, { extId: internal.actual.id, expand: internal.expand }]); } - private removeItem(internal: OwnedCollectionTestItem) { - this.pushDiff([TestDiffOpType.Remove, internal.actual.id]); + private removeItem(childId: string) { + const childItem = this.tree.get(childId); + if (!childItem) { + throw new Error('attempting to remove non-existent child'); + } + + this.pushDiff([TestDiffOpType.Remove, childId]); - const queue: (OwnedCollectionTestItem | undefined)[] = [internal]; + const queue: (OwnedCollectionTestItem | undefined)[] = [childItem]; while (queue.length) { const item = queue.pop(); if (!item) { continue; } - this.tree.delete(item.item.extId); - this.testItemToInternal.delete(item.actual); - for (const child of item.actual.children.values()) { + getPrivateApiFor(item.actual).listener = undefined; + this.tree.delete(item.actual.id); + for (const child of item.actual.children.all) { queue.push(this.tree.get(child.id)); } } diff --git a/src/vs/workbench/contrib/testing/common/testCollection.ts b/src/vs/workbench/contrib/testing/common/testCollection.ts index 2821c77ab34192734a6569951f0fda3b7f4d486e..fe3e5dcdd736cf1dd20184ed29296df163b3853e 100644 --- a/src/vs/workbench/contrib/testing/common/testCollection.ts +++ b/src/vs/workbench/contrib/testing/common/testCollection.ts @@ -10,6 +10,8 @@ import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { TestMessageSeverity, TestResultState } from 'vs/workbench/api/common/extHostTypes'; +export { TestResultState } from 'vs/workbench/api/common/extHostTypes'; + export interface ITestIdWithSrc { testId: string; controllerId: string; @@ -18,6 +20,36 @@ export interface ITestIdWithSrc { export const identifyTest = (test: { controllerId: string, item: { extId: string } }): ITestIdWithSrc => ({ testId: test.item.extId, controllerId: test.controllerId }); +export const enum TestRunConfigurationBitset { + Run = 1 << 1, + Debug = 1 << 2, + Coverage = 1 << 3, + HasNonDefaultConfig = 1 << 4, + HasConfigurable = 1 << 5, +} + +/** + * List of all test run configuration bitset values. + */ +export const testRunConfigurationBitsetList = [ + TestRunConfigurationBitset.Run, + TestRunConfigurationBitset.Debug, + TestRunConfigurationBitset.Coverage, + TestRunConfigurationBitset.HasNonDefaultConfig, +]; + +/** + * DTO for a controller's run configurations. + */ +export interface ITestRunConfiguration { + controllerId: string; + profileId: number; + label: string; + group: TestRunConfigurationBitset; + isDefault: boolean; + hasConfigurationHandler: boolean; +} + /** * Defines the path to a test, as a list of test IDs. The last element of the * array is the test ID, and the predecessors are its parents, in order. @@ -25,12 +57,17 @@ export const identifyTest = (test: { controllerId: string, item: { extId: string export type TestIdPath = string[]; /** - * Request to the main thread to run a set of tests. + * A fully-resolved request to run tests, passsed between the main thread + * and extension host. */ -export interface RunTestsRequest { - tests: ITestIdWithSrc[]; - exclude?: string[]; - debug: boolean; +export interface ResolvedTestRunRequest { + targets: { + testIds: string[]; + controllerId: string; + profileGroup: TestRunConfigurationBitset; + profileId: number; + }[] + exclude?: ITestIdWithSrc[]; isAutoRun?: boolean; } @@ -39,9 +76,10 @@ export interface RunTestsRequest { */ export interface ExtensionRunTestsRequest { id: string; - tests: string[]; + include: string[]; exclude: string[]; - debug: boolean; + controllerId: string; + config?: { group: TestRunConfigurationBitset, id: number }; persist: boolean; } @@ -51,9 +89,9 @@ export interface ExtensionRunTestsRequest { export interface RunTestForControllerRequest { runId: string; controllerId: string; + configId: number; excludeExtIds: string[]; testIds: string[]; - debug: boolean; } /** @@ -97,8 +135,6 @@ export interface ITestItem { range: IRange | null; description: string | null; error: string | IMarkdownString | null; - runnable: boolean; - debuggable: boolean; } export const enum TestItemExpandState { @@ -154,8 +190,6 @@ export interface TestResultItem { retired: boolean; /** Max duration of the item's tasks (if run directly) */ ownDuration?: number; - /** True if the test was directly requested by the run (is not a child or parent) */ - direct?: boolean; /** Controller ID from whence this test came */ controllerId: string; } @@ -179,6 +213,8 @@ export interface ISerializedTestResults { tasks: ITestRunTask[]; /** Human-readable name of the test run. */ name: string; + /** Test trigger informaton */ + request: ResolvedTestRunRequest; } export interface ITestCoverage { diff --git a/src/vs/workbench/contrib/testing/common/testConfigurationService.ts b/src/vs/workbench/contrib/testing/common/testConfigurationService.ts new file mode 100644 index 0000000000000000000000000000000000000000..f6553f228e1d29c7de01a0dac03c107e13fe3857 --- /dev/null +++ b/src/vs/workbench/contrib/testing/common/testConfigurationService.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { isDefined } from 'vs/base/common/types'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; +import { ITestRunConfiguration, TestRunConfigurationBitset, testRunConfigurationBitsetList } from 'vs/workbench/contrib/testing/common/testCollection'; +import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; +import { IMainThreadTestController } from 'vs/workbench/contrib/testing/common/testService'; + +export const ITestConfigurationService = createDecorator('testConfigurationService'); + +export interface ITestConfigurationService { + readonly _serviceBrand: undefined; + + /** + * Fired when any configuration changes. + */ + readonly onDidChange: Event; + + /** + * Publishes a new test configuration. + */ + addConfiguration(controller: IMainThreadTestController, config: ITestRunConfiguration): void; + + /** + * Updates an existing test run configuration + */ + updateConfiguration(controllerId: string, configId: number, update: Partial): void; + + /** + * Removes a configuration. If configId is not given, all configurations + * for the given controller will be removed. + */ + removeConfiguration(controllerId: string, configId?: number): void; + + /** + * Gets capabilities for the given controller by ID, indicating whether + * there's any configurations available for those groups. + * @returns a bitset to use with {@link TestRunConfigurationBitset} + */ + controllerCapabilities(controllerId: string): number; + + /** + * Configures a test configuration. + */ + configure(controllerId: string, configId: number): void; + + /** + * Gets all registered controllers, grouping by controller. + */ + all(): Iterable>; + + /** + * Gets the default configurations to be run for a given run group. + */ + getGroupDefaultConfigurations(group: TestRunConfigurationBitset): ITestRunConfiguration[]; + + /** + * Sets the default configurations to be run for a given run group. + */ + setGroupDefaultConfigurations(group: TestRunConfigurationBitset, configs: ITestRunConfiguration[]): void; + + /** + * Gets the configurations for a controller, in priority order. + */ + getControllerConfigurations(controllerId: string): undefined | { + controller: IMainThreadTestController; + configs: ITestRunConfiguration[]; + }; + + /** + * Gets the configurations for the group in a controller, in priorty order. + */ + getControllerGroupConfigurations(controllerId: string, group: TestRunConfigurationBitset): readonly ITestRunConfiguration[]; +} + +const sorter = (a: ITestRunConfiguration, b: ITestRunConfiguration) => { + if (a.isDefault !== b.isDefault) { + return a.isDefault ? -1 : 1; + } + + return a.label.localeCompare(b.label); +}; + +/** + * Given a capabilities bitset, returns a map of context keys representing + * them. + */ +export const capabilityContextKeys = (capabilities: number): [key: string, value: boolean][] => [ + [TestingContextKeys.hasRunnableTests.key, (capabilities & TestRunConfigurationBitset.Run) !== 0], + [TestingContextKeys.hasDebuggableTests.key, (capabilities & TestRunConfigurationBitset.Debug) !== 0], + [TestingContextKeys.hasCoverableTests.key, (capabilities & TestRunConfigurationBitset.Coverage) !== 0], +]; + + +export class TestConfigurationService implements ITestConfigurationService { + declare readonly _serviceBrand: undefined; + private readonly preferredDefaults: StoredValue<{ [K in TestRunConfigurationBitset]?: { controllerId: string; configId: number }[] }>; + private readonly capabilitiesContexts: { [K in TestRunConfigurationBitset]: IContextKey }; + private readonly changeEmitter = new Emitter(); + private readonly controllerConfigs = new Map(); + + /** @inheritdoc */ + public readonly onDidChange = this.changeEmitter.event; + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IStorageService storageService: IStorageService, + ) { + this.preferredDefaults = new StoredValue({ + key: 'testingPreferredConfigs', + scope: StorageScope.WORKSPACE, + target: StorageTarget.USER, + }, storageService); + + this.capabilitiesContexts = { + [TestRunConfigurationBitset.Run]: TestingContextKeys.hasRunnableTests.bindTo(contextKeyService), + [TestRunConfigurationBitset.Debug]: TestingContextKeys.hasDebuggableTests.bindTo(contextKeyService), + [TestRunConfigurationBitset.Coverage]: TestingContextKeys.hasCoverableTests.bindTo(contextKeyService), + [TestRunConfigurationBitset.HasNonDefaultConfig]: TestingContextKeys.hasNonDefaultProfile.bindTo(contextKeyService), + [TestRunConfigurationBitset.HasConfigurable]: TestingContextKeys.hasConfigurableConfig.bindTo(contextKeyService), + }; + + this.refreshContextKeys(); + } + + /** @inheritdoc */ + public addConfiguration(controller: IMainThreadTestController, config: ITestRunConfiguration): void { + let record = this.controllerConfigs.get(config.controllerId); + if (record) { + record.configs.push(config); + record.configs.sort(sorter); + record.capabilities |= config.group; + } else { + record = { + configs: [config], + controller, + capabilities: config.group + }; + this.controllerConfigs.set(config.controllerId, record); + } + + if (!config.isDefault) { + record.capabilities |= TestRunConfigurationBitset.HasNonDefaultConfig; + } + + this.refreshContextKeys(); + this.changeEmitter.fire(); + } + + /** @inheritdoc */ + public updateConfiguration(controllerId: string, configId: number, update: Partial): void { + const ctrl = this.controllerConfigs.get(controllerId); + if (!ctrl) { + return; + } + + const config = ctrl.configs.find(c => c.controllerId === controllerId && c.profileId === configId); + if (!config) { + return; + } + + Object.assign(config, update); + ctrl.configs.sort(sorter); + this.changeEmitter.fire(); + } + + /** @inheritdoc */ + public configure(controllerId: string, configId: number) { + this.controllerConfigs.get(controllerId)?.controller.configureRunConfig(configId); + } + + /** @inheritdoc */ + public removeConfiguration(controllerId: string, configId?: number): void { + const ctrl = this.controllerConfigs.get(controllerId); + if (!ctrl) { + return; + } + + if (!configId) { + this.controllerConfigs.delete(controllerId); + this.changeEmitter.fire(); + return; + } + + const index = ctrl.configs.findIndex(c => c.profileId === configId); + if (index === -1) { + return; + } + + ctrl.configs.splice(index, 1); + ctrl.capabilities = 0; + for (const { group } of ctrl.configs) { + ctrl.capabilities |= group; + } + + this.refreshContextKeys(); + this.changeEmitter.fire(); + } + + /** @inheritdoc */ + public controllerCapabilities(controllerId: string) { + return this.controllerConfigs.get(controllerId)?.capabilities || 0; + } + + /** @inheritdoc */ + public all() { + return this.controllerConfigs.values(); + } + + /** @inheritdoc */ + public getControllerConfigurations(controllerId: string) { + return this.controllerConfigs.get(controllerId); + } + + /** @inheritdoc */ + public getControllerGroupConfigurations(controllerId: string, group: TestRunConfigurationBitset) { + return this.controllerConfigs.get(controllerId)?.configs.filter(c => c.group === group) ?? []; + } + + /** @inheritdoc */ + public getGroupDefaultConfigurations(group: TestRunConfigurationBitset) { + const preferred = this.preferredDefaults.get(); + if (!preferred) { + return this.getBaseDefaults(group); + } + + const configs = preferred[group] + ?.map(p => this.controllerConfigs.get(p.controllerId)?.configs.find( + c => c.profileId === p.configId && c.group === group)) + .filter(isDefined); + + return configs?.length ? configs : this.getBaseDefaults(group); + } + + /** @inheritdoc */ + public setGroupDefaultConfigurations(group: TestRunConfigurationBitset, configs: ITestRunConfiguration[]) { + this.preferredDefaults.store({ + ...this.preferredDefaults.get(), + [group]: configs.map(c => ({ configId: c.profileId, controllerId: c.controllerId })), + }); + + this.changeEmitter.fire(); + } + + private getBaseDefaults(group: TestRunConfigurationBitset) { + const defaults: ITestRunConfiguration[] = []; + for (const { configs } of this.controllerConfigs.values()) { + const config = configs.find(c => c.group === group); + if (config) { + defaults.push(config); + } + } + + return defaults; + } + + private refreshContextKeys() { + let allCapabilities = 0; + for (const { capabilities } of this.controllerConfigs.values()) { + allCapabilities |= capabilities; + } + + for (const group of testRunConfigurationBitsetList) { + this.capabilitiesContexts[group].set((allCapabilities & group) !== 0); + } + } +} diff --git a/src/vs/workbench/contrib/testing/common/testExclusions.ts b/src/vs/workbench/contrib/testing/common/testExclusions.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a6079595672f6e0ac666c9ac104d21e8a487efd --- /dev/null +++ b/src/vs/workbench/contrib/testing/common/testExclusions.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; +import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; +import { ITestIdWithSrc } from 'vs/workbench/contrib/testing/common/testCollection'; + +export class TestExclusions extends Disposable { + private readonly excluded = this._register( + MutableObservableValue.stored(new StoredValue>({ + key: 'excludedTestItems', + scope: StorageScope.WORKSPACE, + target: StorageTarget.USER, + serialization: { + deserialize: v => new Set(JSON.parse(v)), + serialize: v => JSON.stringify([...v]) + }, + }, this.storageService), new Set()) + ); + + constructor(@IStorageService private readonly storageService: IStorageService) { + super(); + } + + /** + * Event that fires when the excluded tests change. + */ + public readonly onTestExclusionsChanged: Event = this.excluded.onDidChange; + + /** + * Gets whether there's any excluded tests. + */ + public get hasAny() { + return this.excluded.value.size > 0; + } + + /** + * Gets all excluded tests. + */ + public get all() { + return Iterable.map(this.excluded.value, v => { + const [controllerId, testId] = JSON.parse(v); + return { controllerId, testId }; + }); + } + + /** + * Sets whether a test is excluded. + */ + public toggle(test: ITestIdWithSrc, exclude?: boolean): void { + const slug = this.identify(test); + if (exclude !== true && this.excluded.value.has(slug)) { + this.excluded.value = new Set(Iterable.filter(this.excluded.value, e => e !== slug)); + } else if (exclude !== false && !this.excluded.value.has(slug)) { + this.excluded.value = new Set([...this.excluded.value, slug]); + } + } + + /** + * Gets whether a test is excluded. + */ + public contains(test: ITestIdWithSrc): boolean { + return this.excluded.value.has(this.identify(test)); + } + + /** + * Removes all test exclusions. + */ + public clear(): void { + this.excluded.value = new Set(); + } + + private identify(test: ITestIdWithSrc) { + return JSON.stringify([test.controllerId, test.testId]); + } +} diff --git a/src/vs/workbench/contrib/testing/common/testResult.ts b/src/vs/workbench/contrib/testing/common/testResult.ts index e5fceb262381d8294223fc97993ddffa57182c6f..ae6cc612bbd0ee595a9fb4cbbcf34fec35a64973 100644 --- a/src/vs/workbench/contrib/testing/common/testResult.ts +++ b/src/vs/workbench/contrib/testing/common/testResult.ts @@ -13,7 +13,7 @@ import { localize } from 'vs/nls'; import { TestResultState } from 'vs/workbench/api/common/extHostTypes'; import { IComputedStateAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState'; import { IObservableValue, MutableObservableValue, staticObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; -import { ExtensionRunTestsRequest, ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, RunTestsRequest, TestIdPath, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestIdPath, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { maxPriority, statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates'; @@ -44,7 +44,7 @@ export interface ITestResult { /** * Whether this test result is triggered from an auto run. */ - readonly isAutoRun?: boolean; + readonly request: ResolvedTestRunRequest; /** * Human-readable name of the test result. @@ -255,21 +255,6 @@ export class LiveTestResult implements ITestResult { public readonly tasks: ITestRunTaskWithCoverage[] = []; public readonly name = localize('runFinished', 'Test run at {0}', new Date().toLocaleString()); - /** - * Test IDs directly included in this run. - */ - public readonly includedIds: ReadonlySet; - - /** - * Test IDs excluded from this run. - */ - public readonly excludedIds: ReadonlySet; - - /** - * Gets whether this test is from an auto-run. - */ - public readonly isAutoRun: boolean; - /** * @inheritdoc */ @@ -313,11 +298,9 @@ export class LiveTestResult implements ITestResult { constructor( public readonly id: string, public readonly output: LiveOutputController, - private readonly req: ExtensionRunTestsRequest | RunTestsRequest, + public readonly persist: boolean, + public readonly request: ResolvedTestRunRequest, ) { - this.isAutoRun = 'isAutoRun' in this.req && !!this.req.isAutoRun; - this.includedIds = new Set(req.tests.map(t => typeof t === 'string' ? t : t.testId)); - this.excludedIds = new Set(req.exclude); } /** @@ -465,9 +448,7 @@ export class LiveTestResult implements ITestResult { * @inheritdoc */ public toJSON(): ISerializedTestResults | undefined { - return this.completedAt && !('persist' in this.req && this.req.persist === false) - ? this.doSerialize.getValue() - : undefined; + return this.completedAt && this.persist ? this.doSerialize.getValue() : undefined; } /** @@ -504,7 +485,6 @@ export class LiveTestResult implements ITestResult { private addTestToRun(controllerId: string, item: ITestItem, parent: string | null) { const node = itemToNode(controllerId, item, parent); - node.direct = this.includedIds.has(item.extId); this.testById.set(item.extId, node); this.counts[TestResultState.Unset]++; @@ -535,6 +515,7 @@ export class LiveTestResult implements ITestResult { completedAt: this.completedAt!, tasks: this.tasks, name: this.name, + request: this.request, items: [...this.testById.values()].map(entry => ({ ...entry, retired: undefined, @@ -580,6 +561,11 @@ export class HydratedTestResult implements ITestResult { */ public readonly name: string; + /** + * @inheritdoc + */ + public readonly request: ResolvedTestRunRequest; + private readonly testById = new Map(); constructor( @@ -591,6 +577,7 @@ export class HydratedTestResult implements ITestResult { this.completedAt = serialized.completedAt; this.tasks = serialized.tasks.map(task => ({ ...task, coverage: staticObservableValue(undefined) })); this.name = serialized.name; + this.request = serialized.request; for (const item of serialized.items) { const cast: TestResultItem = { ...item, retired: true }; diff --git a/src/vs/workbench/contrib/testing/common/testResultService.ts b/src/vs/workbench/contrib/testing/common/testResultService.ts index c2be63937f2e589454bcc5b0813381e9904c70d0..3ca32a474d2bbe1334e12ba1e59ca9033295d9c4 100644 --- a/src/vs/workbench/contrib/testing/common/testResultService.ts +++ b/src/vs/workbench/contrib/testing/common/testResultService.ts @@ -11,7 +11,8 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TestResultState } from 'vs/workbench/api/common/extHostTypes'; -import { ExtensionRunTestsRequest, RunTestsRequest, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ExtensionRunTestsRequest, ITestRunConfiguration, ResolvedTestRunRequest, TestResultItem, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestResult, LiveTestResult, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultStorage, RETAIN_MAX_RESULTS } from 'vs/workbench/contrib/testing/common/testResultStorage'; @@ -47,7 +48,7 @@ export interface ITestResultService { /** * Creates a new, live test result. */ - createLiveResult(req: RunTestsRequest | ExtensionRunTestsRequest): LiveTestResult; + createLiveResult(req: ResolvedTestRunRequest | ExtensionRunTestsRequest): LiveTestResult; /** * Adds a new test result to the collection. @@ -107,6 +108,7 @@ export class TestResultService implements ITestResultService { constructor( @IContextKeyService contextKeyService: IContextKeyService, @ITestResultStorage private readonly storage: ITestResultStorage, + @ITestConfigurationService private readonly testConfiguration: ITestConfigurationService, ) { this.isRunning = TestingContextKeys.isRunning.bindTo(contextKeyService); this.hasAnyResults = TestingContextKeys.hasAnyResults.bindTo(contextKeyService); @@ -129,13 +131,36 @@ export class TestResultService implements ITestResultService { /** * @inheritdoc */ - public createLiveResult(req: RunTestsRequest | ExtensionRunTestsRequest) { - if ('id' in req) { - return this.push(new LiveTestResult(req.id, this.storage.getOutputController(req.id), req)); - } else { + public createLiveResult(req: ResolvedTestRunRequest | ExtensionRunTestsRequest) { + if ('targets' in req) { const id = generateUuid(); - return this.push(new LiveTestResult(id, this.storage.getOutputController(id), req)); + return this.push(new LiveTestResult(id, this.storage.getOutputController(id), true, req)); + } + + let config: ITestRunConfiguration | undefined; + if (!req.config) { + config = this.testConfiguration.getControllerGroupConfigurations(req.controllerId, TestRunConfigurationBitset.Run)[0]; + } else { + const configs = this.testConfiguration.getControllerGroupConfigurations(req.controllerId, req.config.group); + config = configs.find(c => c.profileId === req.config!.id) || configs[0]; } + + const resolved: ResolvedTestRunRequest = { + targets: [], + exclude: req.exclude.map(testId => ({ testId, controllerId: req.controllerId })), + isAutoRun: false, + }; + + if (config) { + resolved.targets.push({ + profileGroup: config.group, + profileId: config.profileId, + controllerId: req.controllerId, + testIds: req.include, + }); + } + + return this.push(new LiveTestResult(req.id, this.storage.getOutputController(req.id), req.persist, resolved)); } /** diff --git a/src/vs/workbench/contrib/testing/common/testResultStorage.ts b/src/vs/workbench/contrib/testing/common/testResultStorage.ts index 411f9dab95900de4bb990e01dec2a8bc760251af..3ff93398917432d1ab7d7d2845789c560b94e8a9 100644 --- a/src/vs/workbench/contrib/testing/common/testResultStorage.ts +++ b/src/vs/workbench/contrib/testing/common/testResultStorage.ts @@ -43,10 +43,12 @@ export interface ITestResultStorage { export const ITestResultStorage = createDecorator('ITestResultStorage'); +const currentRevision = 0; + export abstract class BaseTestResultStorage implements ITestResultStorage { declare readonly _serviceBrand: undefined; - protected readonly stored = new StoredValue>({ + protected readonly stored = new StoredValue>({ key: 'storedTestResults', scope: StorageScope.WORKSPACE, target: StorageTarget.MACHINE @@ -62,7 +64,11 @@ export abstract class BaseTestResultStorage implements ITestResultStorage { * @override */ public async read(): Promise { - const results = await Promise.all(this.stored.get([]).map(async ({ id }) => { + const results = await Promise.all(this.stored.get([]).map(async ({ id, rev }) => { + if (rev !== currentRevision) { + return undefined; + } + try { const contents = await this.readForResultId(id); if (!contents) { @@ -107,7 +113,7 @@ export abstract class BaseTestResultStorage implements ITestResultStorage { */ public async persist(results: ReadonlyArray): Promise { const toDelete = new Map(this.stored.get([]).map(({ id, bytes }) => [id, bytes])); - const toStore: { id: string; bytes: number }[] = []; + const toStore: { rev: number, id: string; bytes: number }[] = []; const todo: Promise[] = []; let budget = RETAIN_MAX_BYTES; @@ -124,7 +130,7 @@ export abstract class BaseTestResultStorage implements ITestResultStorage { const existingBytes = toDelete.get(result.id); if (existingBytes !== undefined) { toDelete.delete(result.id); - toStore.push({ id: result.id, bytes: existingBytes }); + toStore.push({ id: result.id, rev: currentRevision, bytes: existingBytes }); budget -= existingBytes; continue; } @@ -136,7 +142,7 @@ export abstract class BaseTestResultStorage implements ITestResultStorage { const contents = VSBuffer.fromString(JSON.stringify(obj)); todo.push(this.storeForResultId(result.id, obj)); - toStore.push({ id: result.id, bytes: contents.byteLength }); + toStore.push({ id: result.id, rev: currentRevision, bytes: contents.byteLength }); budget -= contents.byteLength; if (result instanceof LiveTestResult && result.completedAt !== undefined) { @@ -264,12 +270,12 @@ export class TestResultStorage extends BaseTestResultStorage { return; } - const stored = new Set(this.stored.get()?.map(({ id }) => id)); + const stored = new Set(this.stored.get([]).filter(s => s.rev === currentRevision).map(s => s.id)); await Promise.all( children .filter(child => !stored.has(child.name.replace(/\.[a-z]+$/, ''))) - .map(child => this.fileService.del(child.resource)) + .map(child => this.fileService.del(child.resource).catch(() => undefined)) ); } diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index ccb7dfdf461fddeb1c459f862802efe24db2960c..fd81b7bd1f71a0b1bfdba98a4c076d5fb5eb8dc3 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -5,18 +5,22 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; +import * as extpath from 'vs/base/common/extpath'; +import { Iterable } from 'vs/base/common/iterator'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; -import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestIdWithSrc, RunTestForControllerRequest, RunTestsRequest, TestIdPath, TestItemExpandState, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { IObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; +import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestIdWithSrc, ResolvedTestRunRequest, RunTestForControllerRequest, TestIdPath, TestItemExpandState, TestRunConfigurationBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions'; import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult'; -import * as extpath from 'vs/base/common/extpath'; -import { Iterable } from 'vs/base/common/iterator'; export const ITestService = createDecorator('testService'); -export interface MainTestController { +export interface IMainThreadTestController { + readonly id: string; + readonly label: IObservableValue; + configureRunConfig(configId: number): void; expandTest(src: ITestIdWithSrc, levels: number): Promise; runTests(request: RunTestForControllerRequest, token: CancellationToken): Promise; } @@ -161,6 +165,21 @@ export interface ITestRootProvider { // todo: nothing, yet } +/** + * A run request that expresses the intent of the request and allows the + * test service to resolve the specifics of the group. + */ +export interface AmbiguousRunTestsRequest { + /** Group to run */ + group: TestRunConfigurationBitset; + /** Tests to run. Allowed to be from different controllers */ + tests: ITestIdWithSrc[]; + /** Tests to exclude. If not given, the current UI excluded tests are used */ + exclude?: ITestIdWithSrc[]; + /** Whether this was triggered from an auto run. */ + isAutoRun?: boolean; +} + export interface ITestService { readonly _serviceBrand: undefined; /** @@ -170,9 +189,9 @@ export interface ITestService { readonly onDidCancelTestRun: Event<{ runId: string | undefined; }>; /** - * Set of test IDs the user asked to exclude. + * Event that fires when the excluded tests change. */ - readonly excludeTests: MutableObservableValue>; + readonly excluded: TestExclusions; /** * Test collection instance. @@ -185,24 +204,19 @@ export interface ITestService { readonly onDidProcessDiff: Event; /** - * Sets whether a test is excluded. - */ - setTestExcluded(testId: string, exclude?: boolean): void; - - /** - * Removes all test exclusions. + * Registers an interface that runs tests for the given provider ID. */ - clearExcludedTests(): void; + registerTestController(providerId: string, controller: IMainThreadTestController): IDisposable; /** - * Registers an interface that runs tests for the given provider ID. + * Requests that tests be executed. */ - registerTestController(providerId: string, controller: MainTestController): IDisposable; + runTests(req: AmbiguousRunTestsRequest, token?: CancellationToken): Promise; /** * Requests that tests be executed. */ - runTests(req: RunTestsRequest, token?: CancellationToken): Promise; + runResolvedTests(req: ResolvedTestRunRequest, token?: CancellationToken): Promise; /** * Cancels an ongoing test run by its ID, or all runs if no ID is given. @@ -213,9 +227,4 @@ export interface ITestService { * Publishes a test diff for a controller. */ publishDiff(controllerId: string, diff: TestsDiff): void; - - /** - * Requests to resubscribe to all active subscriptions, discarding old tests. - */ - resubscribeToAllTests(): void; } diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts index 30f84c9db12cef0b0a55a61c781423ce995d9a0d..5710997da05f200970f265610926fdc2b302a215 100644 --- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts +++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts @@ -6,31 +6,28 @@ import { groupBy } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { Iterable } from 'vs/base/common/iterator'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection'; -import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; -import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; -import { RunTestsRequest, ITestIdWithSrc, TestsDiff, TestDiffOpType } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestIdWithSrc, ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; +import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; -import { ITestService, MainTestController } from 'vs/workbench/contrib/testing/common/testService'; +import { AmbiguousRunTestsRequest, IMainThreadTestController, ITestService } from 'vs/workbench/contrib/testing/common/testService'; export class TestService extends Disposable implements ITestService { declare readonly _serviceBrand: undefined; - private testControllers = new Map(); + private testControllers = new Map(); private readonly cancelExtensionTestRunEmitter = new Emitter<{ runId: string | undefined }>(); private readonly processDiffEmitter = new Emitter(); private readonly providerCount: IContextKey; - private readonly hasRunnable: IContextKey; - private readonly hasDebuggable: IContextKey; /** * Cancellation for runs requested by the user being managed by the UI. * Test runs initiated by extensions are not included here. @@ -40,42 +37,34 @@ export class TestService extends Disposable implements ITestService { /** * @inheritdoc */ - public readonly excludeTests = MutableObservableValue.stored(new StoredValue>({ - key: 'excludedTestItems', - scope: StorageScope.WORKSPACE, - target: StorageTarget.USER, - serialization: { - deserialize: v => new Set(JSON.parse(v)), - serialize: v => JSON.stringify([...v]) - }, - }, this.storageService), new Set()); + public readonly onDidProcessDiff = this.processDiffEmitter.event; /** * @inheritdoc */ - public readonly onDidProcessDiff = this.processDiffEmitter.event; + public readonly onDidCancelTestRun = this.cancelExtensionTestRunEmitter.event; /** * @inheritdoc */ - public readonly onDidCancelTestRun = this.cancelExtensionTestRunEmitter.event; + public readonly collection = new MainThreadTestCollection(this.expandTest.bind(this)); /** - * @inheritdoc + * @inheritdoc */ - public readonly collection = new MainThreadTestCollection(this.expandTest.bind(this)); + public readonly excluded: TestExclusions; constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IStorageService private readonly storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @ITestConfigurationService private readonly testConfigurationService: ITestConfigurationService, @INotificationService private readonly notificationService: INotificationService, @ITestResultService private readonly testResults: ITestResultService, @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, ) { super(); + this.excluded = instantiationService.createInstance(TestExclusions); this.providerCount = TestingContextKeys.providerCount.bindTo(contextKeyService); - this.hasDebuggable = TestingContextKeys.hasDebuggableTests.bindTo(contextKeyService); - this.hasRunnable = TestingContextKeys.hasRunnableTests.bindTo(contextKeyService); } /** @@ -88,48 +77,65 @@ export class TestService extends Disposable implements ITestService { /** * @inheritdoc */ - public clearExcludedTests() { - this.excludeTests.value = new Set(); - } + public cancelTestRun(runId?: string) { + this.cancelExtensionTestRunEmitter.fire({ runId }); - /** - * @inheritdoc - */ - public setTestExcluded(testId: string, exclude = !this.excludeTests.value.has(testId)) { - const newSet = new Set(this.excludeTests.value); - if (exclude) { - newSet.add(testId); + if (runId === undefined) { + for (const runCts of this.uiRunningTests.values()) { + runCts.cancel(); + } } else { - newSet.delete(testId); - } - - if (newSet.size !== this.excludeTests.value.size) { - this.excludeTests.value = newSet; + this.uiRunningTests.get(runId)?.cancel(); } } - /** * @inheritdoc */ - public cancelTestRun(runId?: string) { - this.cancelExtensionTestRunEmitter.fire({ runId }); + public async runTests(req: AmbiguousRunTestsRequest, token = CancellationToken.None): Promise { + const resolved: ResolvedTestRunRequest = { targets: [], exclude: req.exclude, isAutoRun: req.isAutoRun }; + + // First, try to run the tests using the default run configurations... + const defaultConfigs = this.testConfigurationService.getGroupDefaultConfigurations(req.group); + for (const config of defaultConfigs) { + const testIds = req.tests.filter(t => t.controllerId === config.controllerId).map(t => t.testId); + if (testIds.length) { + resolved.targets.push({ + testIds: testIds, + profileGroup: config.group, + profileId: config.profileId, + controllerId: config.controllerId, + }); + } + } - if (runId === undefined) { - for (const runCts of this.uiRunningTests.values()) { - runCts.cancel(); + // If no tests are covered by the defaults, just use whatever the defaults + // for their controller are. This can happen if the user chose specific + // configs for the run button, but then asked to run a single test from the + // explorer or decoration. We shouldn't no-op. + if (resolved.targets.length === 0) { + for (const byController of groupBy(req.tests, (a, b) => a.controllerId === b.controllerId ? 0 : 1)) { + const configs = this.testConfigurationService.getControllerGroupConfigurations(byController[0].controllerId, req.group); + if (configs.length) { + resolved.targets.push({ + testIds: byController.map(t => t.testId), + profileGroup: req.group, + profileId: configs[0].profileId, + controllerId: configs[0].controllerId, + }); + } } - } else { - this.uiRunningTests.get(runId)?.cancel(); } + + return this.runResolvedTests(resolved, token); } /** * @inheritdoc */ - public async runTests(req: RunTestsRequest, token = CancellationToken.None): Promise { + public async runResolvedTests(req: ResolvedTestRunRequest, token = CancellationToken.None) { if (!req.exclude) { - req.exclude = [...this.excludeTests.value]; + req.exclude = [...this.excluded.all]; } const result = this.testResults.createLiveResult(req); @@ -143,18 +149,17 @@ export class TestService extends Disposable implements ITestService { } try { - const tests = groupBy(req.tests, (a, b) => a.controllerId === b.controllerId ? 0 : 1); const cancelSource = new CancellationTokenSource(token); this.uiRunningTests.set(result.id, cancelSource); - const requests = tests.map( - group => this.testControllers.get(group[0].controllerId)?.runTests( + const requests = req.targets.map( + group => this.testControllers.get(group.controllerId)?.runTests( { runId: result.id, - debug: req.debug, - excludeExtIds: req.exclude ?? [], - testIds: group.map(g => g.testId), - controllerId: group[0].controllerId, + excludeExtIds: req.exclude!.filter(t => t.controllerId === group.controllerId).map(t => t.testId), + configId: group.profileId, + controllerId: group.controllerId, + testIds: group.testIds, }, cancelSource.token, ).catch(err => { @@ -170,27 +175,18 @@ export class TestService extends Disposable implements ITestService { } } - /** - * @inheritdoc - */ - public resubscribeToAllTests() { - // todo - } - /** * @inheritdoc */ public publishDiff(_controllerId: string, diff: TestsDiff) { this.collection.apply(diff); - this.hasDebuggable.set(Iterable.some(this.collection.all, t => t.item.debuggable)); - this.hasRunnable.set(Iterable.some(this.collection.all, t => t.item.runnable)); this.processDiffEmitter.fire(diff); } /** * @inheritdoc */ - public registerTestController(id: string, controller: MainTestController): IDisposable { + public registerTestController(id: string, controller: IMainThreadTestController): IDisposable { this.testControllers.set(id, controller); this.providerCount.set(this.testControllers.size); diff --git a/src/vs/workbench/contrib/testing/common/testStubs.ts b/src/vs/workbench/contrib/testing/common/testStubs.ts index 10350931505dbac18823fb880d810d0f15591873..589cea74f21bc5b4d6897916c74b087239d7cd6b 100644 --- a/src/vs/workbench/contrib/testing/common/testStubs.ts +++ b/src/vs/workbench/contrib/testing/common/testStubs.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { TestItemImpl, TestResultState } from 'vs/workbench/api/common/extHostTypes'; +import { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection'; import { TestSingleUseCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection'; export * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; -export { TestItemImpl, TestResultState } from 'vs/workbench/api/common/extHostTypes'; +export { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi'; /** * Gets a main thread test collection initialized with the given set of @@ -29,17 +29,18 @@ export const testStubs = { collection.root.canResolveChildren = true; collection.resolveHandler = item => { if (item === collection.root) { - const a = new TestItemImpl(idPrefix + 'a', 'a', URI.file('/'), undefined, collection.root); + const a = new TestItemImpl(idPrefix + 'a', 'a', URI.file('/')); a.canResolveChildren = true; - new TestItemImpl(idPrefix + 'b', 'b', URI.file('/'), undefined, collection.root); + const b = new TestItemImpl(idPrefix + 'b', 'b', URI.file('/')); + item.children.all = [a, b]; } else if (item.id === idPrefix + 'a') { - new TestItemImpl(idPrefix + 'aa', 'aa', URI.file('/'), undefined, item); - new TestItemImpl(idPrefix + 'ab', 'ab', URI.file('/'), undefined, item); + item.children.all = [ + new TestItemImpl(idPrefix + 'aa', 'aa', URI.file('/')), + new TestItemImpl(idPrefix + 'ab', 'ab', URI.file('/')), + ]; } }; return collection; }, }; - -export const ReExportedTestRunState = TestResultState; diff --git a/src/vs/workbench/contrib/testing/common/testingAutoRun.ts b/src/vs/workbench/contrib/testing/common/testingAutoRun.ts index 605fdb9ab906f3be9e65926547e308aa05252754..59f43fee9c3a7a4950f8db667860aff466836c14 100644 --- a/src/vs/workbench/contrib/testing/common/testingAutoRun.ts +++ b/src/vs/workbench/contrib/testing/common/testingAutoRun.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { AutoRunMode, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration'; -import { identifyTest, ITestIdWithSrc, TestDiffOpType } from 'vs/workbench/contrib/testing/common/testCollection'; +import { identifyTest, ITestIdWithSrc, TestDiffOpType, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult'; import { isRunningTests, ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; @@ -83,7 +83,7 @@ export class TestingAutoRun extends Disposable implements ITestingAutoRun { const tests = [...rerunIds.values()]; rerunIds.clear(); - await this.testService.runTests({ debug: false, tests, isAutoRun: true }); + await this.testService.runTests({ group: TestRunConfigurationBitset.Run, tests, isAutoRun: true }); if (rerunIds.size > 0) { scheduler.schedule(delay); diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index b9bfb4975959041181045306a7fda51c01419c91..f43e40840fbb8c420382d6ef3478c70628aca563 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -7,11 +7,24 @@ import { localize } from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { TestExplorerViewMode, TestExplorerViewSorting } from 'vs/workbench/contrib/testing/common/constants'; +import { TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; export namespace TestingContextKeys { export const providerCount = new RawContextKey('testing.providerCount', 0); - export const hasDebuggableTests = new RawContextKey('testing.hasDebuggableTests', false); - export const hasRunnableTests = new RawContextKey('testing.hasRunnableTests', false); + export const hasDebuggableTests = new RawContextKey('testing.hasDebuggableTests', false, { type: 'boolean', description: localize('testing.hasDebuggableTests', 'Indicates whether any test controller has registered a debug configuration') }); + export const hasRunnableTests = new RawContextKey('testing.hasRunnableTests', false, { type: 'boolean', description: localize('testing.hasRunnableTests', 'Indicates whether any test controller has registered a run configuration') }); + export const hasCoverableTests = new RawContextKey('testing.hasCoverableTests', false, { type: 'boolean', description: localize('testing.hasCoverableTests', 'Indicates whether any test controller has registered a coverage configuration') }); + export const hasNonDefaultProfile = new RawContextKey('testing.hasNonDefaultConfig', false, { type: 'boolean', description: localize('testing.hasNonDefaultConfig', 'Indicates whether any test controller has registered a non-default configuration') }); + export const hasConfigurableConfig = new RawContextKey('testing.hasConfigurableConfig', false, { type: 'boolean', description: localize('testing.hasConfigurableConfig', 'Indicates whether any test configuration can be configured') }); + + export const capabilityToContextKey: { [K in TestRunConfigurationBitset]: RawContextKey } = { + [TestRunConfigurationBitset.Run]: hasRunnableTests, + [TestRunConfigurationBitset.Coverage]: hasCoverableTests, + [TestRunConfigurationBitset.Debug]: hasDebuggableTests, + [TestRunConfigurationBitset.HasNonDefaultConfig]: hasNonDefaultProfile, + [TestRunConfigurationBitset.HasConfigurable]: hasConfigurableConfig, + }; + export const hasAnyResults = new RawContextKey('testing.hasAnyResults', false); export const viewMode = new RawContextKey('testing.explorerViewMode', TestExplorerViewMode.List); export const viewSorting = new RawContextKey('testing.explorerViewSorting', TestExplorerViewSorting.ByLocation); diff --git a/src/vs/workbench/contrib/testing/common/testingStates.ts b/src/vs/workbench/contrib/testing/common/testingStates.ts index f237e0b09b91e8e8e50c36e367282700d30ea68a..84c9cf019feb1eab1c3131cf054afa4653f7c811 100644 --- a/src/vs/workbench/contrib/testing/common/testingStates.ts +++ b/src/vs/workbench/contrib/testing/common/testingStates.ts @@ -5,6 +5,8 @@ import { TestResultState } from 'vs/workbench/api/common/extHostTypes'; +export { TestResultState } from 'vs/workbench/api/common/extHostTypes'; + export type TreeStateNode = { statusNode: true; state: TestResultState; priority: number }; /** diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts index 2d8e8aacb917ede0079249bfdc9bcb6560978bda..9403cfd947cbff3b440b561fbcdafe2e1506c7d0 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts @@ -7,8 +7,9 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation'; import { TestDiffOpType, TestItemExpandState, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; +import { TestResultState } from 'vs/workbench/contrib/testing/common/testingStates'; import { TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult'; -import { Convert, TestItemImpl, TestResultState } from 'vs/workbench/contrib/testing/common/testStubs'; +import { Convert, TestItemImpl } from 'vs/workbench/contrib/testing/common/testStubs'; import { TestTreeTestHarness } from 'vs/workbench/contrib/testing/test/browser/testObjectTree'; class TestHierarchicalByLocationProjection extends HierarchicalByLocationProjection { @@ -53,10 +54,10 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { harness.flush(); harness.pushDiff([ TestDiffOpType.Add, - { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'c', undefined, undefined, undefined)) }, + { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'c', undefined)) }, ], [ TestDiffOpType.Add, - { controllerId: 'ctrl2', parent: 'c', expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'ca', undefined, undefined, undefined)) }, + { controllerId: 'ctrl2', parent: 'c', expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'ca', undefined)) }, ]); assert.deepStrictEqual(harness.flush(), [ @@ -74,7 +75,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { { e: 'b' } ]); - new TestItemImpl('ac', 'ac', undefined, undefined, harness.c.root.children.get('id-a')!); + harness.c.root.children.get('id-a')!.children.add(new TestItemImpl('ac', 'ac', undefined)); assert.deepStrictEqual(harness.flush(), [ { e: 'a', children: [{ e: 'aa' }, { e: 'ab' }, { e: 'ac' }] }, @@ -91,7 +92,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { { e: 'b' } ]); - harness.c.root.children.get('id-a')!.children.get('id-ab')!.dispose(); + harness.c.root.children.get('id-a')!.children.remove('id-ab'); assert.deepStrictEqual(harness.flush(), [ { e: 'a', children: [{ e: 'aa' }] }, @@ -104,7 +105,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { resultsService.getStateById = () => [undefined, resultInState(TestResultState.Failed)]; const resultInState = (state: TestResultState): TestResultItem => ({ - item: harness.c.itemToInternal.get(harness.c.root.children.get('id-a')!)!.item, + item: Convert.TestItem.from(harness.c.tree.get('id-a')!.actual), parent: 'id-root', tasks: [], retired: false, diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts index fe70f44ea5105390d258c19f6988f08ca820dcb4..9803ebd3e37b5a42582130ce9e89b27be143c8c3 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts @@ -42,10 +42,10 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { harness.flush(); harness.pushDiff([ TestDiffOpType.Add, - { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'root2', undefined, undefined, undefined)) }, + { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'root2', undefined)) }, ], [ TestDiffOpType.Add, - { controllerId: 'ctrl2', parent: 'c', expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'c', undefined, undefined, undefined)) }, + { controllerId: 'ctrl2', parent: 'c', expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'c', undefined)) }, ]); assert.deepStrictEqual(harness.flush(), [ @@ -57,7 +57,7 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { test('updates nodes if they add children', async () => { harness.flush(); - new TestItemImpl('ac', 'ac', undefined, undefined, harness.c.root.children.get('id-a')!); + harness.c.root.children.get('id-a')!.children.add(new TestItemImpl('ac', 'ac', undefined)); assert.deepStrictEqual(harness.flush(), [ { e: 'aa' }, @@ -69,7 +69,7 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { test('updates nodes if they remove children', async () => { harness.flush(); - harness.c.root.children.get('id-a')!.children.get('id-ab')!.dispose(); + harness.c.root.children.get('id-a')!.children.remove('id-ab'); assert.deepStrictEqual(harness.flush(), [ { e: 'aa' }, @@ -79,7 +79,7 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { test('swaps when node is no longer leaf', async () => { harness.flush(); - new TestItemImpl('ba', 'ba', undefined, undefined, harness.c.root.children.get('id-b')!); + harness.c.root.children.get('id-b')!.children.add(new TestItemImpl('ba', 'ba', undefined)); assert.deepStrictEqual(harness.flush(), [ { e: 'aa' }, @@ -87,17 +87,5 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { { e: 'ba' }, ]); }); - - test('swaps when node is no longer runnable', async () => { - harness.flush(); - const ba = new TestItemImpl('ba', 'ba', undefined, undefined, harness.c.root.children.get('id-b')!); - ba.runnable = false; - - assert.deepStrictEqual(harness.flush(), [ - { e: 'aa' }, - { e: 'ab' }, - { e: 'b' }, - ]); - }); }); diff --git a/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts b/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts index 3c88575f3c287bb54ccf01e4c1f08e66677d4b98..ce29ffa8119d3d72291f0178e614d617130fff06 100644 --- a/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts +++ b/src/vs/workbench/contrib/testing/test/common/ownedTestCollection.ts @@ -7,10 +7,6 @@ import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/own import { TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; export class TestSingleUseCollection extends SingleUseTestCollection { - public get itemToInternal() { - return this.testItemToInternal; - } - public get currentDiff() { return this.diff; } diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 01a4df4463f8d4cd5cce28f571763ba658a694e6..c8cb04450cb8f3c26aaec8cad0635296b2ae9314 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -10,11 +10,13 @@ import { Lazy } from 'vs/base/common/lazy'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { NullLogService } from 'vs/platform/log/common/log'; import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection'; -import { ITestTaskState, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestTaskState, ResolvedTestRunRequest, TestResultItem, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection'; +import { TestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService'; +import { TestResultState } from 'vs/workbench/contrib/testing/common/testingStates'; import { getPathForTestInResult, HydratedTestResult, LiveOutputController, LiveTestResult, makeEmptyCounts, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult'; import { TestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage'; -import { Convert, getInitializedMainTestCollection, ReExportedTestRunState as TestRunState, TestResultState, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; +import { Convert, getInitializedMainTestCollection, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; export const emptyOutputController = () => new LiveOutputController( @@ -32,12 +34,14 @@ suite('Workbench - Test Results Service', () => { let changed = new Set(); let tests: SingleUseTestCollection; - const defaultOpts = { - exclude: [], - debug: false, - id: 'x', - persist: true, - }; + const defaultOpts = (testIds: string[]): ResolvedTestRunRequest => ({ + targets: [{ + profileGroup: TestRunConfigurationBitset.Run, + profileId: 0, + controllerId: 'ctrlId', + testIds, + }] + }); class TestLiveTestResult extends LiveTestResult { public override setAllToState(state: TestResultState, taskId: string, when: (task: ITestTaskState, item: TestResultItem) => boolean) { @@ -50,7 +54,8 @@ suite('Workbench - Test Results Service', () => { r = new TestLiveTestResult( 'foo', emptyOutputController(), - { ...defaultOpts, tests: ['id-a'] }, + true, + defaultOpts(['id-a']), ); r.onChange(e => changed.add(e)); @@ -75,7 +80,8 @@ suite('Workbench - Test Results Service', () => { assert.deepStrictEqual(getLabelsIn(new TestLiveTestResult( 'foo', emptyOutputController(), - { ...defaultOpts, tests: ['id-a'] }, + false, + defaultOpts(['id-a']), ).tests), []); }); @@ -95,29 +101,29 @@ suite('Workbench - Test Results Service', () => { test('initializes with valid counts', () => { assert.deepStrictEqual(r.counts, { ...makeEmptyCounts(), - [TestRunState.Queued]: 2, - [TestRunState.Unset]: 2, + [TestResultState.Queued]: 2, + [TestResultState.Unset]: 2, }); }); test('setAllToState', () => { changed.clear(); - r.setAllToState(TestRunState.Queued, 't', (_, t) => t.item.label !== 'root'); + r.setAllToState(TestResultState.Queued, 't', (_, t) => t.item.label !== 'root'); assert.deepStrictEqual(r.counts, { ...makeEmptyCounts(), - [TestRunState.Unset]: 1, - [TestRunState.Queued]: 3, + [TestResultState.Unset]: 1, + [TestResultState.Queued]: 3, }); - r.setAllToState(TestRunState.Failed, 't', (_, t) => t.item.label !== 'root'); + r.setAllToState(TestResultState.Failed, 't', (_, t) => t.item.label !== 'root'); assert.deepStrictEqual(r.counts, { ...makeEmptyCounts(), - [TestRunState.Unset]: 1, - [TestRunState.Failed]: 3, + [TestResultState.Unset]: 1, + [TestResultState.Failed]: 3, }); - assert.deepStrictEqual(r.getStateById('id-a')?.ownComputedState, TestRunState.Failed); - assert.deepStrictEqual(r.getStateById('id-a')?.tasks[0].state, TestRunState.Failed); + assert.deepStrictEqual(r.getStateById('id-a')?.ownComputedState, TestResultState.Failed); + assert.deepStrictEqual(r.getStateById('id-a')?.tasks[0].state, TestResultState.Failed); assert.deepStrictEqual(getChangeSummary(), [ { label: 'a', reason: TestResultItemChangeReason.OwnStateChange }, { label: 'aa', reason: TestResultItemChangeReason.OwnStateChange }, @@ -128,16 +134,16 @@ suite('Workbench - Test Results Service', () => { test('updateState', () => { changed.clear(); - r.updateState('id-aa', 't', TestRunState.Running); + r.updateState('id-aa', 't', TestResultState.Running); assert.deepStrictEqual(r.counts, { ...makeEmptyCounts(), - [TestRunState.Unset]: 2, - [TestRunState.Running]: 1, - [TestRunState.Queued]: 1, + [TestResultState.Unset]: 2, + [TestResultState.Running]: 1, + [TestResultState.Queued]: 1, }); - assert.deepStrictEqual(r.getStateById('id-aa')?.ownComputedState, TestRunState.Running); + assert.deepStrictEqual(r.getStateById('id-aa')?.ownComputedState, TestResultState.Running); // update computed state: - assert.deepStrictEqual(r.getStateById(tests.root.id)?.computedState, TestRunState.Running); + assert.deepStrictEqual(r.getStateById(tests.root.id)?.computedState, TestResultState.Running); assert.deepStrictEqual(getChangeSummary(), [ { label: 'a', reason: TestResultItemChangeReason.ComputedStateChange }, { label: 'aa', reason: TestResultItemChangeReason.OwnStateChange }, @@ -161,30 +167,30 @@ suite('Workbench - Test Results Service', () => { test('ignores outside run', () => { changed.clear(); - r.updateState('id-b', 't', TestRunState.Running); + r.updateState('id-b', 't', TestResultState.Running); assert.deepStrictEqual(r.counts, { ...makeEmptyCounts(), - [TestRunState.Queued]: 2, - [TestRunState.Unset]: 2, + [TestResultState.Queued]: 2, + [TestResultState.Unset]: 2, }); assert.deepStrictEqual(r.getStateById('id-b'), undefined); }); test('markComplete', () => { - r.setAllToState(TestRunState.Queued, 't', () => true); - r.updateState('id-aa', 't', TestRunState.Passed); + r.setAllToState(TestResultState.Queued, 't', () => true); + r.updateState('id-aa', 't', TestResultState.Passed); changed.clear(); r.markComplete(); assert.deepStrictEqual(r.counts, { ...makeEmptyCounts(), - [TestRunState.Passed]: 1, - [TestRunState.Unset]: 3, + [TestResultState.Passed]: 1, + [TestResultState.Unset]: 3, }); - assert.deepStrictEqual(r.getStateById(tests.root.id)?.ownComputedState, TestRunState.Unset); - assert.deepStrictEqual(r.getStateById('id-aa')?.ownComputedState, TestRunState.Passed); + assert.deepStrictEqual(r.getStateById(tests.root.id)?.ownComputedState, TestResultState.Unset); + assert.deepStrictEqual(r.getStateById('id-aa')?.ownComputedState, TestResultState.Passed); }); }); @@ -198,7 +204,7 @@ suite('Workbench - Test Results Service', () => { setup(() => { storage = new InMemoryResultStorage(new TestStorageService(), new NullLogService()); - results = new TestTestResultService(new MockContextKeyService(), storage); + results = new TestTestResultService(new MockContextKeyService(), storage, new TestConfigurationService(new MockContextKeyService(), new TestStorageService())); }); test('pushes new result', () => { @@ -208,17 +214,18 @@ suite('Workbench - Test Results Service', () => { test('serializes and re-hydrates', async () => { results.push(r); - r.updateState('id-aa', 't', TestRunState.Passed); + r.updateState('id-aa', 't', TestResultState.Passed); r.markComplete(); - await timeout(0); // allow persistImmediately async to happen + await timeout(10); // allow persistImmediately async to happen results = new TestResultService( new MockContextKeyService(), storage, + new TestConfigurationService(new MockContextKeyService(), new TestStorageService()), ); assert.strictEqual(0, results.results.length); - await timeout(0); // allow load promise to resolve + await timeout(10); // allow load promise to resolve assert.strictEqual(1, results.results.length); const [rehydrated, actual] = results.getStateById(tests.root.id)!; @@ -240,7 +247,8 @@ suite('Workbench - Test Results Service', () => { const r2 = results.push(new LiveTestResult( '', emptyOutputController(), - { ...defaultOpts, tests: [] } + false, + defaultOpts([]), )); results.clear(); @@ -252,7 +260,8 @@ suite('Workbench - Test Results Service', () => { const r2 = results.push(new LiveTestResult( '', emptyOutputController(), - { ...defaultOpts, tests: [] } + false, + defaultOpts([]), )); assert.deepStrictEqual(results.results, [r2, r]); @@ -262,11 +271,12 @@ suite('Workbench - Test Results Service', () => { assert.deepStrictEqual(results.results, [r, r2]); }); - const makeHydrated = async (completedAt = 42, state = TestRunState.Passed) => new HydratedTestResult({ + const makeHydrated = async (completedAt = 42, state = TestResultState.Passed) => new HydratedTestResult({ completedAt, id: 'some-id', tasks: [{ id: 't', running: false, name: undefined }], name: 'hello world', + request: defaultOpts([]), items: [{ ...(await getInitializedMainTestCollection()).getNodeById('id-a')!, tasks: [{ state, duration: 0, messages: [] }], diff --git a/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts index 6e5411524c84071b9024ee06b4ac2ebc466c92d7..56a49f73aa810fdd3982c437e2e909833d312d9d 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultStorage.test.ts @@ -19,13 +19,8 @@ suite('Workbench - Test Result Storage', () => { const t = new LiveTestResult( '', emptyOutputController(), - { - tests: [], - exclude: [], - debug: false, - id: 'x', - persist: true, - } + true, + { targets: [] } ); t.addTask({ id: 't', name: undefined, running: true }); diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts new file mode 100644 index 0000000000000000000000000000000000000000..bbd519a11b252ded2c38968c1aea69eb90343454 --- /dev/null +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { Event } from 'vs/base/common/event'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { localize } from 'vs/nls'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { TypeHierarchyProviderRegistry } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; + + +const _ctxHasTypeHierarchyProvider = new RawContextKey('editorHasTypeHierarchyProvider', false, localize('editorHasTypeHierarchyProvider', 'Whether a type hierarchy provider is available')); + +class TypeHierarchyController implements IEditorContribution { + static readonly Id = 'typeHierarchy'; + + static get(editor: ICodeEditor): TypeHierarchyController { + return editor.getContribution(TypeHierarchyController.Id); + } + + private readonly _ctxHasProvider: IContextKey; + private readonly _dispoables = new DisposableStore(); + private readonly _sessionDisposables = new DisposableStore(); + + constructor( + readonly _editor: ICodeEditor, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + ) { + this._ctxHasProvider = _ctxHasTypeHierarchyProvider.bindTo(this._contextKeyService); + this._dispoables.add(Event.any(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, TypeHierarchyProviderRegistry.onDidChange)(() => { + this._ctxHasProvider.set(_editor.hasModel() && TypeHierarchyProviderRegistry.has(_editor.getModel())); + })); + this._dispoables.add(this._sessionDisposables); + } + + dispose(): void { + this._dispoables.dispose(); + } +} + +registerEditorContribution(TypeHierarchyController.Id, TypeHierarchyController); + +// Testing diff --git a/src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts b/src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts new file mode 100644 index 0000000000000000000000000000000000000000..d395256a847285faa285c2bc1d3e93c8b50090ea --- /dev/null +++ b/src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts @@ -0,0 +1,186 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRange, Range } from 'vs/editor/common/core/range'; +import { SymbolKind, ProviderResult, SymbolTag } from 'vs/editor/common/modes'; +import { ITextModel } from 'vs/editor/common/model'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; +import { URI } from 'vs/base/common/uri'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { IDisposable, RefCountedDisposable } from 'vs/base/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + + + +export interface TypeHierarchyItem { + _sessionId: string; + _itemId: string; + name: string; + kind: SymbolKind; + detail?: string; + uri: URI; + range: IRange; + selectionRange: IRange; + tags?: SymbolTag[] +} + +export interface TypeHierarchySession { + roots: TypeHierarchyItem[]; + dispose(): void; +} + +export interface TypeHierarchyProvider { + prepareTypeHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult; + provideSupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; + provideSubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; +} + +export const TypeHierarchyProviderRegistry = new LanguageFeatureRegistry(); + + + +export class TypeHierarchyModel { + + static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise { + const [provider] = TypeHierarchyProviderRegistry.ordered(model); + if (!provider) { + return undefined; + } + const session = await provider.prepareTypeHierarchy(model, position, token); + if (!session) { + return undefined; + } + return new TypeHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposable(session)); + } + + readonly root: TypeHierarchyItem; + + private constructor( + readonly id: string, + readonly provider: TypeHierarchyProvider, + readonly roots: TypeHierarchyItem[], + readonly ref: RefCountedDisposable, + ) { + this.root = roots[0]; + } + + dispose(): void { + this.ref.release(); + } + + fork(item: TypeHierarchyItem): TypeHierarchyModel { + const that = this; + return new class extends TypeHierarchyModel { + constructor() { + super(that.id, that.provider, [item], that.ref.acquire()); + } + }; + } + + async provideSupertypes(item: TypeHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideSupertypes(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } + return []; + } + + async provideSubtypes(item: TypeHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideSubtypes(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } + return []; + } +} + +// --- API command support + +const _models = new Map(); + +CommandsRegistry.registerCommand('_executePrepareTypeHierarchy', async (accessor, ...args) => { + const [resource, position] = args; + assertType(URI.isUri(resource)); + assertType(Position.isIPosition(position)); + + const modelService = accessor.get(IModelService); + let textModel = modelService.getModel(resource); + let textModelReference: IDisposable | undefined; + if (!textModel) { + const textModelService = accessor.get(ITextModelService); + const result = await textModelService.createModelReference(resource); + textModel = result.object.textEditorModel; + textModelReference = result; + } + + try { + const model = await TypeHierarchyModel.create(textModel, position, CancellationToken.None); + if (!model) { + return []; + } + + _models.set(model.id, model); + _models.forEach((value, key, map) => { + if (map.size > 10) { + value.dispose(); + _models.delete(key); + } + }); + return [model.root]; + + } finally { + textModelReference?.dispose(); + } +}); + +function isTypeHierarchyItemDto(obj: any): obj is TypeHierarchyItem { + const item = obj as TypeHierarchyItem; + return typeof obj === 'object' + && typeof item.name === 'string' + && typeof item.kind === 'number' + && URI.isUri(item.uri) + && Range.isIRange(item.range) + && Range.isIRange(item.selectionRange); +} + +CommandsRegistry.registerCommand('_executeProvideSupertypes', async (_accessor, ...args) => { + const [item] = args; + assertType(isTypeHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.provideSupertypes(item, CancellationToken.None); +}); + +CommandsRegistry.registerCommand('_executeProvideSubtypes', async (_accessor, ...args) => { + const [item] = args; + assertType(isTypeHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.provideSubtypes(item, CancellationToken.None); +}); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts index 7912b6ca068170b1232f2a3eaca9f1a953ee85e0..55e6fc940f4e15d5cfe0a8a87914877f89e4fd96 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -5,18 +5,18 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { SettingsEditor2Input, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { isWeb } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IEditorInput } from 'vs/workbench/common/editor'; -import { IViewsService } from 'vs/workbench/common/views'; import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { isWeb } from 'vs/base/common/platform'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { IViewsService } from 'vs/workbench/common/views'; +import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; +import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; export class UserDataSyncTrigger extends Disposable implements IWorkbenchContribution { @@ -52,9 +52,6 @@ export class UserDataSyncTrigger extends Disposable implements IWorkbenchContrib if (editorInput instanceof SettingsEditor2Input) { return 'settingsEditor'; } - if (editorInput instanceof PreferencesEditorInput) { - return 'settingsEditor'; - } if (editorInput instanceof KeybindingsEditorInput) { return 'keybindingsEditor'; } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts index 0d80c344783327fbe74f421bb40c5f5f4216aa7d..286a51b0104691a064f6e15395a095594f3beab0 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts @@ -25,6 +25,8 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { EditorResolution } from 'vs/platform/editor/common/editor'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export * as icons from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons'; @@ -238,19 +240,59 @@ registerAction2(class extends Action2 { id: x.id, label: x.title, detail: x.description, - })), { canPickMany: false, title: localize('pickWalkthroughs', "Open Walkthrough...") }); + description: x.source, + })), { canPickMany: false, matchOnDescription: true, matchOnDetail: true, title: localize('pickWalkthroughs', "Open Walkthrough...") }); if (selection) { commandService.executeCommand('workbench.action.openWalkthrough', selection.id); } } }); +const prefersReducedMotionConfig = { + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.welcomePage.preferReducedMotion': { + scope: ConfigurationScope.APPLICATION, + type: 'boolean', + default: true, + description: localize('workbench.welcomePage.preferReducedMotion', "When enabled, reduce motion in welcome page.") + } + } +} as const; + +const prefersStandardMotionConfig = { + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.welcomePage.preferReducedMotion': { + scope: ConfigurationScope.APPLICATION, + type: 'boolean', + default: false, + description: localize('workbench.welcomePage.preferReducedMotion', "When enabled, reduce motion in welcome page.") + } + } +} as const; + class WorkbenchConfigurationContribution { constructor( @IInstantiationService _instantiationService: IInstantiationService, @IGettingStartedService _gettingStartedService: IGettingStartedService, + @IConfigurationService _configurationService: IConfigurationService, + @ITASExperimentService _experimentSevice: ITASExperimentService, ) { // Init the getting started service via DI. + this.registerConfigs(_experimentSevice); + } + + private async registerConfigs(_experimentSevice: ITASExperimentService) { + const preferReduced = await _experimentSevice.getTreatment('welcomePage.preferReducedMotion').catch(e => false); + if (preferReduced) { + configurationRegistry.deregisterConfigurations([prefersStandardMotionConfig]); + configurationRegistry.registerConfiguration(prefersReducedMotionConfig); + } + else { + configurationRegistry.deregisterConfigurations([prefersReducedMotionConfig]); + configurationRegistry.registerConfiguration(prefersStandardMotionConfig); + } } } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index a27d091d223649f7b9eed87ef12cdb85e6fbad46..a0ae5e362f6f87a31356da343f3cebba8bb1be99 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -87,7 +87,7 @@ padding: 12px 24px; } -.monaco-workbench .part.editor>.content .gettingStartedContainer.animationReady .gettingStartedSlide { +.monaco-workbench .part.editor>.content .gettingStartedContainer.animatable .gettingStartedSlide { /* keep consistant with SLIDE_TRANSITION_TIME_MS in gettingStarted.ts */ transition: left 0.25s, opacity 0.25s; } @@ -363,6 +363,9 @@ width: 100%; margin: 4px 0; overflow: hidden; +} + +.monaco-workbench .part.editor>.content .gettingStartedContainer.animatable .gettingStartedSlideDetails .getting-started-step { transition: height .1s linear; } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 9c68c51c6d2c54759f722ac467c8d2e716d5fad8..11c76014d097f8e6a1e2ab4c0ee625a5c8d01ea9 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -78,6 +78,7 @@ type GettingStartedActionEvent = { argument: string | undefined; }; +const REDUCED_MOTION_KEY = 'workbench.welcomePage.preferReducedMotion'; export class GettingStartedPage extends EditorPane { public static readonly ID = 'gettingStartedPage'; @@ -218,6 +219,12 @@ export class GettingStartedPage extends EditorPane { this.hideCategory(category.id); } + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(REDUCED_MOTION_KEY)) { + this.container.classList.toggle('animatable', this.shouldAnimate()); + } + })); + ourStep.done = step.done; if (category.id === this.currentCategory?.id) { const badgeelements = assertIsDefined(document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); @@ -240,12 +247,18 @@ export class GettingStartedPage extends EditorPane { this.recentlyOpened = workspacesService.getRecentlyOpened(); } + private shouldAnimate() { + return !this.configurationService.getValue(REDUCED_MOTION_KEY); + } + override async setInput(newInput: GettingStartedInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { - this.container.classList.remove('animationReady'); + this.container.classList.remove('animatable'); this.editorInput = newInput; await super.setInput(newInput, options, context, token); await this.buildCategoriesSlide(); - setTimeout(() => this.container.classList.add('animationReady'), 0); + if (this.shouldAnimate()) { + setTimeout(() => this.container.classList.add('animatable'), 0); + } } async makeCategoryVisibleWhenAvailable(categoryID: string, stepId?: string) { diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/extensions.svg b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/extensions.svg index f7a1625e78001ea3cb8fb99d89cbdce9523c7d98..4250fd5265b759e7c7140ea4c8af8aa0917af7ee 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/extensions.svg +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/extensions.svg @@ -41,42 +41,79 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg index 69278e6940bb8442b25427c3208c76890180359a..5d719827f4e304ddf19fae59469d5ba88f6130f8 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg @@ -1,82 +1,88 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg index faffe54db392cbd7a2f6d097bd204fa7a6b8d973..23417ed8b8845609e70ef707257af11cfa3a65f8 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg @@ -1,4 +1,7 @@ + + + @@ -89,4 +92,6 @@ + + diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index ac0a88a65809d89d69f4e0ea0376148d9029d6a4..d5b04f8c643ce031cf757a99d887a58b23c640ae 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -36,8 +36,7 @@ import { dirname, resolve } from 'vs/base/common/path'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import product from 'vs/platform/product/common/product'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { Schemas } from 'vs/base/common/network'; +import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { splitName } from 'vs/base/common/labels'; @@ -76,10 +75,12 @@ export class WorkspaceTrustContextKeys extends Disposable implements IWorkbenchC ) { super(); - this._ctxWorkspaceTrustState = WorkspaceTrustContext.IsTrusted.bindTo(contextKeyService); this._ctxWorkspaceTrustEnabled = WorkspaceTrustContext.IsEnabled.bindTo(contextKeyService); this._ctxWorkspaceTrustEnabled.set(workspaceTrustEnablementService.isWorkspaceTrustEnabled()); + this._ctxWorkspaceTrustState = WorkspaceTrustContext.IsTrusted.bindTo(contextKeyService); + this._ctxWorkspaceTrustState.set(workspaceTrustManagementService.isWorkspaceTrusted()); + this._register(workspaceTrustManagementService.onDidChangeTrust(trusted => this._ctxWorkspaceTrustState.set(trusted))); } } @@ -400,9 +401,8 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon let checkboxText: string | undefined; const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())!; const isSingleFolderWorkspace = isSingleFolderWorkspaceIdentifier(workspaceIdentifier); - if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) { - const { parentPath } = splitName(workspaceIdentifier.uri.fsPath); - const { name } = splitName(parentPath); + if (this.workspaceTrustManagementService.canSetParentFolderTrust()) { + const { name } = splitName(splitName((workspaceIdentifier as ISingleFolderWorkspaceIdentifier).uri.fsPath).parentPath); checkboxText = localize('checkboxString', "Trust the authors of all files in the parent folder '{0}'", name); } diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 6f384e56f2d9d1940a11787022be00bc282d31f0..aea3204e4768ca570c9bc5ac72fbebfed58c8f74 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -39,7 +39,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { attachButtonStyler, attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; -import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ISingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ChoiceAction } from 'vs/workbench/common/notifications'; @@ -942,8 +942,8 @@ export class WorkspaceTrustEditor extends EditorPane { }) ]; - const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()); - if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) { + if (this.workspaceTrustManagementService.canSetParentFolderTrust()) { + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()) as ISingleFolderWorkspaceIdentifier; const { parentPath } = splitName(workspaceIdentifier.uri.fsPath); const { name } = splitName(parentPath); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index b4f4bec0a93b69e0a71ab56a56c42dd2427c282a..ff5f3fdfb3fa7b4a9fbfb95fbd2e26520dd23bcb 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -276,16 +276,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment extensionDevelopmentLocationURI: undefined, extensionDevelopmentKind: undefined }; - const developmentOptions = this.options.developmentOptions; - if (developmentOptions) { - if (developmentOptions.extensions?.length) { - extensionHostDebugEnvironment.extensionDevelopmentLocationURI = developmentOptions.extensions.map(e => URI.revive(e)); - extensionHostDebugEnvironment.isExtensionDevelopment = true; - } - if (developmentOptions) { - extensionHostDebugEnvironment.extensionTestsLocationURI = URI.revive(developmentOptions.extensionTestsPath); - } - } // Fill in selected extra environmental properties if (this.payload) { @@ -324,6 +314,17 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment } } + const developmentOptions = this.options.developmentOptions; + if (developmentOptions && !extensionHostDebugEnvironment.isExtensionDevelopment) { + if (developmentOptions.extensions?.length) { + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = developmentOptions.extensions.map(e => URI.revive(e)); + extensionHostDebugEnvironment.isExtensionDevelopment = true; + } + if (developmentOptions.extensionTestsPath) { + extensionHostDebugEnvironment.extensionTestsLocationURI = URI.revive(developmentOptions.extensionTestsPath); + } + } + return extensionHostDebugEnvironment; } } diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 9d04361b275b8a900b145eba80801180d451a39c..e4ca4d6a3806f3f96e3f53a97af56ac18cdcfcbb 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -238,7 +238,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { } // Run Extension Host as fork of current process - this._extensionHostProcess = fork(FileAccess.asFileUri('bootstrap-fork', require).fsPath, ['--type=extensionHost'], opts); + this._extensionHostProcess = fork(FileAccess.asFileUri('bootstrap-fork', require).fsPath, ['--type=extensionHost', '--skipWorkspaceStorageLock'], opts); // Catch all output coming from the extension host process type Output = { data: string, format: string[] }; diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 6e83553289731a6a0030c7a35744d5e69438b689..e6d56c8ff7b75be69b1d2dec8813c70d052ce588 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -22,12 +22,14 @@ import { Promises } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { boolean } from 'vs/editor/common/config/editorOptions'; import 'vs/workbench/api/common/extHost.common.services'; import 'vs/workbench/api/node/extHost.node.services'; interface ParsedExtHostArgs { uriTransformerPath?: string; + skipWorkspaceStorageLock?: boolean; useHostProxy?: string; } @@ -46,6 +48,9 @@ const args = minimist(process.argv.slice(2), { string: [ 'uriTransformerPath', 'useHostProxy' + ], + boolean: [ + 'skipWorkspaceStorageLock' ] }) as ParsedExtHostArgs; @@ -323,6 +328,7 @@ export async function startExtensionHostProcess(): Promise { // setup things patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/microsoft/vscode/issues/37708) initData.environment.useHostProxy = args.useHostProxy !== undefined ? args.useHostProxy !== 'false' : undefined; + initData.environment.skipWorkspaceStorageLock = boolean(args.skipWorkspaceStorageLock, false); // host abstraction const hostUtils = new class NodeHost implements IHostUtils { diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index 86c356e987a56bcdd9d1ad6df7a1f891848841a5..240f8e8dcbe5f3ecf50b30bee23f0ae9a95efe22 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -31,6 +31,7 @@ declare namespace self { let dispatchEvent: any; let indexedDB: { open: any, [k: string]: any }; let caches: { open: any, [k: string]: any }; + let importScripts: any; } const nativeClose = self.close.bind(self); @@ -39,6 +40,8 @@ self.close = () => console.trace(`'close' has been blocked`); const nativePostMessage = postMessage.bind(self); self.postMessage = () => console.trace(`'postMessage' has been blocked`); +self.importScripts = () => { throw new Error(`'importScripts' has been blocked`); }; + // const nativeAddEventListener = addEventListener.bind(self); self.addEventListener = () => console.trace(`'addEventListener' has been blocked`); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 93bf5070dcd236731668ccf8e22c043994614ad4..1cc65ef33c55517692c1079a416ce666518579d2 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; import * as network from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; @@ -15,34 +17,33 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Extensions, getDefaultValue, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { validateSettingsEditorOptions, DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, getSettingsTargetName, IKeybindingsEditorOptions, IKeybindingsEditorPane, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; -import { DefaultPreferencesEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; -import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel, DefaultRawSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; +import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IKeybindingsEditorOptions, IKeybindingsEditorPane, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, USE_SPLIT_JSON_SETTING, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; +import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultRawSettingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { getDefaultValue, IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; -import { getErrorMessage } from 'vs/base/common/errors'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; -import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; const emptyEditableSettingsContent = '{\n}'; @@ -50,8 +51,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic declare readonly _serviceBrand: undefined; - private lastOpenedSettingsInput: PreferencesEditorInput | null = null; - private readonly _onDispose = this._register(new Emitter()); private _defaultUserSettingsUriCounter = 0; @@ -164,7 +163,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return Promise.resolve(null); } - async createPreferencesEditorModel(uri: URI): Promise | null> { + public async createPreferencesEditorModel(uri: URI): Promise | null> { if (this.isDefaultSettingsResource(uri)) { return this.createDefaultSettingsEditorModel(uri); } @@ -211,10 +210,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.openSettings2({ query: query }); } - const editorInput = this.getActiveSettingsEditorInput() || this.lastOpenedSettingsInput; - const resource = editorInput ? editorInput.primary.resource! : this.userSettingsResource; - const target = this.getConfigurationTargetFromSettingsResource(resource); - return this.openOrSwitchSettings(target, resource, { query: query }); + return this.openSettingsJson(ConfigurationTarget.USER_LOCAL, this.userSettingsResource, { query: query }); } private openSettings2(options?: ISettingsEditorOptions): Promise { @@ -233,7 +229,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic jsonEditor; return jsonEditor ? - this.openOrSwitchSettings(ConfigurationTarget.USER_LOCAL, this.userSettingsResource, options, group) : + this.openSettingsJson(ConfigurationTarget.USER_LOCAL, this.userSettingsResource, options, group) : this.openOrSwitchSettings2(ConfigurationTarget.USER_LOCAL, undefined, options, group); } @@ -257,7 +253,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } return jsonEditor ? - this.openOrSwitchSettings(ConfigurationTarget.WORKSPACE, this.workspaceSettingsResource, options, group) : + this.openSettingsJson(ConfigurationTarget.WORKSPACE, this.workspaceSettingsResource, options, group) : this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group); } @@ -268,22 +264,13 @@ export class PreferencesService extends Disposable implements IPreferencesServic const folderSettingsUri = await this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, folder); if (jsonEditor) { if (folderSettingsUri) { - return this.openOrSwitchSettings(ConfigurationTarget.WORKSPACE_FOLDER, folderSettingsUri, options, group); + return this.openSettingsJson(ConfigurationTarget.WORKSPACE_FOLDER, folderSettingsUri, options, group); } return Promise.reject(`Invalid folder URI - ${folder.toString()}`); } return this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE_FOLDER, folder, options, group); } - switchSettings(target: ConfigurationTarget, resource: URI): Promise { - const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane?.input instanceof PreferencesEditorInput) { - return this.doSwitchSettings(target, resource, activeEditorPane.input, activeEditorPane.group).then(() => undefined); - } else { - return this.doOpenSettings(target, resource).then(() => undefined); - } - } - async openGlobalKeybindingSettings(textual: boolean, options?: IKeybindingsEditorOptions): Promise { type OpenKeybindingsClassification = { textual: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -322,28 +309,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } - getCurrentOrNewSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI, group: IEditorGroup): IEditorInput { - const activeEditorInput = this.getActiveSettingsEditorInput(group); - if (activeEditorInput) { - const editorInputResource = activeEditorInput.primary.resource; - if (editorInputResource && editorInputResource.fsPath === resource.fsPath) { - return activeEditorInput; - } - } - - const editableSettingsEditorInput = this.editorService.createEditorInput({ resource }); - return this.createSplitJsonEditorInput(configurationTarget, resource, editableSettingsEditorInput); - } - - private async openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { - const editorInput = this.getActiveSettingsEditorInput(group); - if (editorInput) { - const editorInputResource = editorInput.primary.resource; - if (editorInputResource && editorInputResource.fsPath !== resource.fsPath) { - return this.doSwitchSettings(configurationTarget, resource, editorInput, group, options); - } - } - const editor = await this.doOpenSettings(configurationTarget, resource, options, group); + private async openSettingsJson(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + const editor = await this.doOpenSettingsJson(configurationTarget, resource, options, group); if (editor && options?.revealSetting) { await this.revealSetting(options.revealSetting.key, !!options.revealSetting.edit, editor, resource); } @@ -359,99 +326,37 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.openSettings2(settingsOptions); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettingsJson(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); - if (openSplitJSON) { + const openDefaultSettings = !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING); + if (openSplitJSON || openDefaultSettings) { return this.doOpenSplitJSON(configurationTarget, resource, options, group); } - const openDefaultSettings = !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING); - return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { options = { ...options, pinned: true }; - - if (openDefaultSettings) { - const activeEditorGroup = this.editorGroupService.activeGroup; - const sideEditorGroup = this.editorGroupService.addGroup(activeEditorGroup.id, GroupDirection.RIGHT); - return Promise.all([ - this.editorService.openEditor({ resource: this.defaultSettingsRawResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true, override: EditorResolution.DISABLED }, label: nls.localize('defaultSettings', "Default Settings"), description: '' }), - this.editorService.openEditor(editableSettingsEditorInput, { pinned: true, revealIfOpened: true, override: EditorResolution.DISABLED }, sideEditorGroup.id) - ]).then(([defaultEditor, editor]) => withNullAsUndefined(editor)); - } else { - return this.editorService.openEditor(editableSettingsEditorInput, validateSettingsEditorOptions(options), group); - } + return this.editorService.openEditor(editableSettingsEditorInput, validateSettingsEditorOptions(options), group); }); } - private async doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { - const editableSettingsEditorInput = await this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource); - const preferencesEditorInput = this.createSplitJsonEditorInput(configurationTarget, resource, editableSettingsEditorInput); - if (!options) { - options = { pinned: true }; - } else { - options = { ...options, pinned: true }; - } - + private async doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options: ISettingsEditorOptions = {}, group?: IEditorGroup): Promise { + await this.createSettingsIfNotExists(configurationTarget, resource); + const preferencesEditorInput = this.createSplitJsonEditorInput(configurationTarget, resource); + options = { ...options, pinned: true }; return this.editorService.openEditor(preferencesEditorInput, validateSettingsEditorOptions(options), group); } - private createSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI, editableSettingsEditorInput: IEditorInput): IEditorInput { - const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget)); - const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, editableSettingsEditorInput); - this.lastOpenedSettingsInput = preferencesEditorInput; - - return preferencesEditorInput; + public createSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI): IEditorInput { + const editableSettingsEditorInput = this.editorService.createEditorInput({ resource }); + const defaultPreferencesEditorInput = this.instantiationService.createInstance(TextResourceEditorInput, this.getDefaultSettingsResource(configurationTarget), undefined, undefined, undefined, undefined); + return new SideBySideEditorInput(editableSettingsEditorInput.getName(), undefined, defaultPreferencesEditorInput, editableSettingsEditorInput); } public createSettings2EditorModel(): Settings2EditorModel { return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); } - private async doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise { - const settingsURI = await this.getEditableSettingsURI(target, resource); - if (!settingsURI) { - return Promise.reject(`Invalid settings URI - ${resource.toString()}`); - } - return this.getOrCreateEditableSettingsEditorInput(target, settingsURI) - .then(toInput => { - return group.openEditor(input).then(() => { - const replaceWith = new PreferencesEditorInput(this.getPreferencesEditorInputName(target, resource), toInput.getDescription(), this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(target)), toInput); - - return group.replaceEditors([{ - editor: input, - replacement: replaceWith, - options: validateSettingsEditorOptions(options ?? {}) - }]).then(() => { - this.lastOpenedSettingsInput = replaceWith; - return group.activeEditorPane!; - }); - }); - }); - } - - private getActiveSettingsEditorInput(group: IEditorGroup = this.editorGroupService.activeGroup): PreferencesEditorInput { - return group.editors.filter(e => e instanceof PreferencesEditorInput)[0]; - } - - private getConfigurationTargetFromSettingsResource(resource: URI): ConfigurationTarget { - if (this.userSettingsResource.toString() === resource.toString()) { - return ConfigurationTarget.USER_LOCAL; - } - - const workspaceSettingsResource = this.workspaceSettingsResource; - if (workspaceSettingsResource && workspaceSettingsResource.toString() === resource.toString()) { - return ConfigurationTarget.WORKSPACE; - } - - const folder = this.contextService.getWorkspaceFolder(resource); - if (folder) { - return ConfigurationTarget.WORKSPACE_FOLDER; - } - - return ConfigurationTarget.USER_LOCAL; - } - private getConfigurationTargetFromDefaultSettingsResource(uri: URI) { return this.isDefaultWorkspaceSettingsResource(uri) ? ConfigurationTarget.WORKSPACE : @@ -486,11 +391,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultUserSettingsUriCounter++}/settings.json` }); } - private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string { - const name = getSettingsTargetName(target, resource, this.contextService); - return target === ConfigurationTarget.WORKSPACE_FOLDER ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name; - } - private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): Promise { return this.createSettingsIfNotExists(target, resource) .then(() => this.editorService.createEditorInput({ resource })); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 20de82a3c0890f45b59ff0ffd81b1f4bbb9dde5b..0e8cc11a4084c9bc4aa593200715566d6d252120 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -5,22 +5,20 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; +import { IMatch } from 'vs/base/common/filters'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; -import { IJSONSchemaMap, IJSONSchema } from 'vs/base/common/jsonSchema'; import { ITextModel } from 'vs/editor/common/model'; -import { localize } from 'vs/nls'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, EditPresentationTypes, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; import { EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { IMatch } from 'vs/base/common/filters'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export enum SettingValueType { Null = 'null', @@ -205,8 +203,8 @@ export interface IPreferencesService { workspaceSettingsResource: URI | null; getFolderSettingsResource(resource: URI): URI | null; + createPreferencesEditorModel(uri: URI): Promise | null>; resolveModel(uri: URI): Promise; - createPreferencesEditorModel(uri: URI): Promise | null>; createSettings2EditorModel(): Settings2EditorModel; // TODO openRawDefaultSettings(): Promise; @@ -215,26 +213,11 @@ export interface IPreferencesService { openRemoteSettings(): Promise; openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - switchSettings(target: ConfigurationTarget, resource: URI): Promise; openGlobalKeybindingSettings(textual: boolean, options?: IKeybindingsEditorOptions): Promise; openDefaultKeybindingsFile(): Promise; getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise; - getCurrentOrNewSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI | undefined, group: IEditorGroup): IEditorInput; -} - -export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { - switch (target) { - case ConfigurationTarget.USER: - case ConfigurationTarget.USER_LOCAL: - return localize('userSettingsTarget', "User Settings"); - case ConfigurationTarget.WORKSPACE: - return localize('workspaceSettingsTarget', "Workspace Settings"); - case ConfigurationTarget.WORKSPACE_FOLDER: - const folder = workspaceContextService.getWorkspaceFolder(resource); - return folder ? folder.name : ''; - } - return ''; + createSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI): IEditorInput; } export interface KeybindingMatch { diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 4ce887fd9b38d83afbc2172162348080ee9f7684..978bf921c0eaededfc73ba99dbc0d31907e1bc2e 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -5,66 +5,11 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IEditorInput, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { IEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; -import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; - -export class PreferencesEditorInput extends SideBySideEditorInput { - static override readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; - - override get typeId(): string { - return PreferencesEditorInput.ID; - } - - override get editorId(): string | undefined { - return this.typeId; - } - - override getTitle(verbosity: Verbosity): string { - return this.primary.getTitle(verbosity); - } - - override matches(otherInput: IEditorInput | IUntypedEditorInput): boolean { - return super.matches(otherInput) || this.primary.matches(otherInput); - } -} - -export class DefaultPreferencesEditorInput extends TextResourceEditorInput { - static override readonly ID = 'workbench.editorinputs.defaultpreferences'; - constructor( - defaultSettingsResource: URI, - @ITextModelService textModelResolverService: ITextModelService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService - ) { - super(defaultSettingsResource, nls.localize('settingsEditorName', "Default Settings"), '', undefined, undefined, textModelResolverService, textFileService, editorService, fileService, labelService); - } - - override get typeId(): string { - return DefaultPreferencesEditorInput.ID; - } - - override matches(other: IEditorInput | IUntypedEditorInput): boolean { - if (other instanceof DefaultPreferencesEditorInput) { - return true; - } - if (!super.matches(other)) { - return false; - } - return true; - } -} export interface IKeybindingsEditorSearchOptions { searchValue: string; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 8a65a5a8361b39f988498c5b1b84949e4d36409f..c50d3326028fefeb139437c8659b016f5f7223a4 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -721,14 +721,9 @@ export class DefaultSettings extends Disposable { private toContent(settingsGroups: ISettingsGroup[]): string { const builder = new SettingsContentBuilder(); - builder.pushLine('['); settingsGroups.forEach((settingsGroup, i) => { - builder.pushGroup(settingsGroup); - if (i < settingsGroups.length - 1) { - builder.pushLine(','); - } + builder.pushGroup(settingsGroup, i === 0, i === settingsGroups.length - 1); }); - builder.pushLine(']'); return builder.getContent(); } @@ -941,10 +936,8 @@ class SettingsContentBuilder { this._contentByLines.push(...lineText); } - pushGroup(settingsGroups: ISettingsGroup): void { - this._contentByLines.push('{'); - this._contentByLines.push(''); - this._contentByLines.push(''); + pushGroup(settingsGroups: ISettingsGroup, isFirst?: boolean, isLast?: boolean): void { + this._contentByLines.push(isFirst ? '[{' : '{'); const lastSetting = this._pushGroup(settingsGroups, ' '); if (lastSetting) { @@ -954,7 +947,7 @@ class SettingsContentBuilder { this._contentByLines[lineIdx - 2] = content.substring(0, content.length - 1); } - this._contentByLines.push('}'); + this._contentByLines.push(isLast ? '}]' : '},'); } protected _pushGroup(group: ISettingsGroup, indent: string): ISetting | null { diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index a6a37b5f64e56ee6a018efc59293beaf36828e01..d0fb87951fab6906fb2c9918bd6078ce66537594 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -20,14 +20,14 @@ import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogA class WebTelemetryAppender implements ITelemetryAppender { - constructor(private _appender: IRemoteAgentService) { } + constructor(private _appender: ITelemetryAppender) { } log(eventName: string, data: any): void { - this._appender.logTelemetry(eventName, data); + this._appender.log(eventName, data); } flush(): Promise { - return this._appender.flushTelemetry(); + return this._appender.flush(); } } @@ -49,8 +49,9 @@ export class TelemetryService extends Disposable implements ITelemetryService { super(); if (!!productService.enableTelemetry) { + const telemetryProvider = environmentService.options && environmentService.options.telemetryAppender || { log: remoteAgentService.logTelemetry, flush: remoteAgentService.flushTelemetry }; const config: ITelemetryServiceConfig = { - appender: combinedAppender(new WebTelemetryAppender(remoteAgentService), new TelemetryLogAppender(loggerService, environmentService)), + appender: combinedAppender(new WebTelemetryAppender(telemetryProvider), new TelemetryLogAppender(loggerService, environmentService)), commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), sendErrorTelemetry: false, }; diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 5c1ce7f57313428ee7ab42172a295559a76d9faf..14bf5f2fe2121bdf01dd79679b05e8f8a31d8c6d 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -16,7 +16,7 @@ import { getRemoteAuthority, isVirtualResource } from 'vs/platform/remote/common import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse, IWorkspaceTrustEnablementService } from 'vs/platform/workspace/common/workspaceTrust'; -import { isSingleFolderWorkspaceIdentifier, isUntitledWorkspace, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isUntitledWorkspace, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; @@ -479,13 +479,25 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork canSetParentFolderTrust(): boolean { const workspaceIdentifier = toWorkspaceIdentifier(this._canonicalWorkspace); - return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file; + + if (!isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + return false; + } + + if (workspaceIdentifier.uri.scheme !== Schemas.file) { + return false; + } + + if (splitName((workspaceIdentifier as ISingleFolderWorkspaceIdentifier).uri.fsPath).parentPath === '') { + return false; + } + + return true; } async setParentFolderTrust(trusted: boolean): Promise { - const workspaceIdentifier = toWorkspaceIdentifier(this._canonicalWorkspace); - if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) { - const { parentPath } = splitName(workspaceIdentifier.uri.fsPath); + if (this.canSetParentFolderTrust()) { + const { parentPath } = splitName((toWorkspaceIdentifier(this._canonicalWorkspace) as ISingleFolderWorkspaceIdentifier).uri.fsPath); await this.setUrisTrust([URI.file(parentPath)], trusted); } diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 531aa8c028e3ce62fa471108ca18d58f40e59a57..4b22daaa1ee1e3d93e19a750fb4439a04b19e2ca 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -1292,7 +1292,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.ok(value[0].parent); }); - // --- call hierarcht + // --- call hierarchy test('CallHierarchy, back and forth', async function () { @@ -1335,6 +1335,40 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(outgoing[0].to.name, 'OUTGOING'); }); + // --- type hierarchy + + test('TypeHierarchy, back and forth', async function () { + + + disposables.push(extHost.registerTypeHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.TypeHierarchyProvider { + prepareTypeHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { + return [new types.TypeHierarchyItem(types.SymbolKind.Constant, 'ROOT', 'ROOT', document.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0))]; + } + provideTypeHierarchySupertypes(item: vscode.TypeHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + return [new types.TypeHierarchyItem(types.SymbolKind.Constant, 'SUPER', 'SUPER', item.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0))]; + } + provideTypeHierarchySubtypes(item: vscode.TypeHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + return [new types.TypeHierarchyItem(types.SymbolKind.Constant, 'SUB', 'SUB', item.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0))]; + } + })); + + await rpcProtocol.sync(); + + const root = await commands.executeCommand('vscode.prepareTypeHierarchy', model.uri, new types.Position(0, 0)); + + assert.ok(Array.isArray(root)); + assert.strictEqual(root.length, 1); + assert.strictEqual(root[0].name, 'ROOT'); + + const incoming = await commands.executeCommand('vscode.provideSupertypes', root[0]); + assert.strictEqual(incoming.length, 1); + assert.strictEqual(incoming[0].name, 'SUPER'); + + const outgoing = await commands.executeCommand('vscode.provideSubtypes', root[0]); + assert.strictEqual(outgoing.length, 1); + assert.strictEqual(outgoing[0].name, 'SUB'); + }); + test('selectionRangeProvider on inner array always returns outer array #91852', async function () { disposables.push(extHost.registerSelectionRangeProvider(nullExtensionDescription, defaultSelector, { diff --git a/src/vs/workbench/test/browser/api/extHostTesting.test.ts b/src/vs/workbench/test/browser/api/extHostTesting.test.ts index 6b0bc21e2958f3fb632b12a58e4a524750094c77..30e92d70ffbbadc2eb12f0bf81ea986f1b60a606 100644 --- a/src/vs/workbench/test/browser/api/extHostTesting.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTesting.test.ts @@ -9,11 +9,11 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { mockObject, MockObject } from 'vs/base/test/common/mock'; import { MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; -import { TestRunCoordinator, TestRunDto } from 'vs/workbench/api/common/extHostTesting'; +import { TestRunConfigurationImpl, TestRunCoordinator, TestRunDto } from 'vs/workbench/api/common/extHostTesting'; import * as convert from 'vs/workbench/api/common/extHostTypeConverters'; -import { TestMessage } from 'vs/workbench/api/common/extHostTypes'; +import { TestMessage, TestResultState, TestRunConfigurationGroup } from 'vs/workbench/api/common/extHostTypes'; import { TestDiffOpType, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection'; -import { TestItemImpl, TestResultState, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; +import { TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; import { TestSingleUseCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection'; import type { TestItem, TestRunRequest } from 'vscode'; @@ -22,8 +22,6 @@ const simplify = (item: TestItem) => ({ label: item.label, uri: item.uri, range: item.range, - runnable: item.runnable, - debuggable: item.debuggable, }); const assertTreesEqual = (a: TestItem | undefined, b: TestItem | undefined) => { @@ -37,8 +35,8 @@ const assertTreesEqual = (a: TestItem | undefined, b: TestItem | undefined) => { assert.deepStrictEqual(simplify(a), simplify(b)); - const aChildren = [...a.children.keys()].slice().sort(); - const bChildren = [...b.children.keys()].slice().sort(); + const aChildren = a.children.all.map(c => c.id).sort(); + const bChildren = b.children.all.map(c => c.id).sort(); assert.strictEqual(aChildren.length, bChildren.length, `expected ${a.label}.children.length == ${b.label}.children.length`); aChildren.forEach(key => assertTreesEqual(a.children.get(key), b.children.get(key))); }; @@ -111,6 +109,16 @@ suite('ExtHost Testing', () => { ]); }); + test('parents are set correctly', () => { + single.expand(single.root.id, Infinity); + single.collectDiff(); + + const a = single.root.children.get('id-a')!; + const ab = a.children.get('id-ab')!; + assert.strictEqual(a.parent, undefined); + assert.strictEqual(ab.parent, a); + }); + test('no-ops if items not changed', () => { single.collectDiff(); assert.deepStrictEqual(single.collectDiff(), []); @@ -131,19 +139,20 @@ suite('ExtHost Testing', () => { test('removes children', () => { single.expand(single.root.id, Infinity); single.collectDiff(); - single.root.children.get('id-a')!.dispose(); + single.root.children.remove('id-a'); assert.deepStrictEqual(single.collectDiff(), [ [TestDiffOpType.Remove, 'id-a'], ]); - assert.deepStrictEqual([...single.tree].map(n => n.item.extId).sort(), [single.root.id, 'id-b']); - assert.strictEqual(single.itemToInternal.size, 2); + assert.deepStrictEqual([...single.tree].map(n => n.actual.id).sort(), [single.root.id, 'id-b']); + assert.strictEqual(single.tree.size, 2); }); test('adds new children', () => { single.expand(single.root.id, Infinity); single.collectDiff(); - const child = new TestItemImpl('id-ac', 'c', undefined, undefined, single.root.children.get('id-a')); + const child = new TestItemImpl('id-ac', 'c', undefined); + single.root.children.get('id-a')!.children.add(child); assert.deepStrictEqual(single.collectDiff(), [ [TestDiffOpType.Add, { @@ -154,10 +163,111 @@ suite('ExtHost Testing', () => { }], ]); assert.deepStrictEqual( - [...single.tree].map(n => n.item.extId).sort(), + [...single.tree].map(n => n.actual.id).sort(), [single.root.id, 'id-a', 'id-aa', 'id-ab', 'id-ac', 'id-b'], ); - assert.strictEqual(single.itemToInternal.size, 6); + assert.strictEqual(single.tree.size, 6); + }); + + test('treats in-place replacement as mutation', () => { + single.expand(single.root.id, Infinity); + single.collectDiff(); + + const oldA = single.root.children.get('id-a')!; + const newA = new TestItemImpl('id-a', 'Hello world', undefined); + newA.children.all = oldA.children.all; + single.root.children.all = [ + newA, + new TestItemImpl('id-b', single.root.children.get('id-b')!.label, undefined), + ]; + + assert.deepStrictEqual(single.collectDiff(), [ + [ + TestDiffOpType.Update, + { extId: 'id-a', expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } }, + ], + ]); + + newA.label = 'still connected'; + assert.deepStrictEqual(single.collectDiff(), [ + [ + TestDiffOpType.Update, + { extId: 'id-a', item: { label: 'still connected' } } + ], + ]); + + oldA.label = 'no longer connected'; + assert.deepStrictEqual(single.collectDiff(), []); + }); + + test('treats in-place replacement as mutation deeply', () => { + single.expand(single.root.id, Infinity); + single.collectDiff(); + + const oldA = single.root.children.get('id-a')!; + const newA = new TestItemImpl('id-a', single.root.children.get('id-a')!.label, undefined); + const oldAA = oldA.children.get('id-aa')!; + const oldAB = oldA.children.get('id-ab')!; + const newAB = new TestItemImpl('id-ab', 'Hello world', undefined); + newA.children.all = [oldAA, newAB]; + single.root.children.all = [newA, single.root.children.get('id-b')!]; + + assert.deepStrictEqual(single.collectDiff(), [ + [ + TestDiffOpType.Update, + { extId: 'id-a', expand: TestItemExpandState.Expanded }, + ], + [ + TestDiffOpType.Update, + { extId: 'id-ab', item: { label: 'Hello world' } }, + ], + ]); + + oldAA.label = 'still connected1'; + newAB.label = 'still connected2'; + oldAB.label = 'not connected3'; + assert.deepStrictEqual(single.collectDiff(), [ + [ + TestDiffOpType.Update, + { extId: 'id-aa', item: { label: 'still connected1' } } + ], + [ + TestDiffOpType.Update, + { extId: 'id-ab', item: { label: 'still connected2' } } + ], + ]); + + assert.strictEqual(newAB.parent, newA); + assert.strictEqual(oldAA.parent, newA); + assert.deepStrictEqual(newA.parent, undefined); + }); + + test('moves an item to be a new child', () => { + single.collectDiff(); + const b = single.root.children.get('id-b')!; + const a = single.root.children.get('id-a')!; + a.children.add(b); + assert.deepStrictEqual(single.collectDiff(), [ + [ + TestDiffOpType.Remove, + 'id-b', + ], + [ + TestDiffOpType.Add, + { controllerId: 'ctrlId', parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(single.tree.get('id-b')!.actual) } + ], + ]); + + b.label = 'still connected'; + assert.deepStrictEqual(single.collectDiff(), [ + [ + TestDiffOpType.Update, + { extId: 'id-b', item: { label: 'still connected' } } + ], + ]); + + assert.deepStrictEqual(single.root.children.all, [single.root.children.get('id-a')]); + assert.deepStrictEqual(b.parent, a); }); }); @@ -302,6 +412,7 @@ suite('ExtHost Testing', () => { let proxy: MockObject; let c: TestRunCoordinator; let cts: CancellationTokenSource; + let configuration: TestRunConfigurationImpl; let req: TestRunRequest; @@ -312,27 +423,29 @@ suite('ExtHost Testing', () => { cts = new CancellationTokenSource(); c = new TestRunCoordinator(proxy); + configuration = new TestRunConfigurationImpl(mockObject(), 'ctrlId', 42, 'Do Run', TestRunConfigurationGroup.Run, () => { }, false); + req = { - tests: [single.root], + include: undefined, exclude: [single.root.children.get('id-b')!], - debug: false, + configuration, }; dto = TestRunDto.fromInternal({ controllerId: 'ctrl', - debug: false, + configId: configuration.configId, excludeExtIds: ['id-b'], runId: 'run-id', testIds: [single.root.id], - }); + }, single); }); test('tracks a run started from a main thread request', () => { const tracker = c.prepareForMainThreadTestRun(req, dto, cts.token); assert.strictEqual(tracker.isRunning, false); - const task1 = c.createTestRun('ctrl', req, 'run1', true); - const task2 = c.createTestRun('ctrl', req, 'run2', true); + const task1 = c.createTestRun('ctrl', single, req, 'run1', true); + const task2 = c.createTestRun('ctrl', single, req, 'run2', true); assert.strictEqual(proxy.$startedExtensionTestRun.called, false); assert.strictEqual(tracker.isRunning, true); @@ -350,22 +463,23 @@ suite('ExtHost Testing', () => { }); test('tracks a run started from an extension request', () => { - const task1 = c.createTestRun('ctrl', req, 'hello world', false); + const task1 = c.createTestRun('ctrl', single, req, 'hello world', false); const tracker = Iterable.first(c.trackers)!; assert.strictEqual(tracker.isRunning, true); assert.deepStrictEqual(proxy.$startedExtensionTestRun.args, [ [{ + config: { group: 2, id: 42 }, + controllerId: 'ctrl', id: tracker.id, - tests: [single.root.id], + include: [single.root.id], exclude: ['id-b'], - debug: false, persist: false, }] ]); - const task2 = c.createTestRun('ctrl', req, 'run2', true); - const task3Detached = c.createTestRun('ctrl', { ...req }, 'task3Detached', true); + const task2 = c.createTestRun('ctrl', single, req, 'run2', true); + const task3Detached = c.createTestRun('ctrl', single, { ...req }, 'task3Detached', true); task1.end(); assert.strictEqual(proxy.$finishedExtensionTestRun.called, false); @@ -379,7 +493,7 @@ suite('ExtHost Testing', () => { }); test('adds tests to run smartly', () => { - const task1 = c.createTestRun('ctrl', req, 'hello world', false); + const task1 = c.createTestRun('ctrl', single, req, 'hello world', false); const tracker = Iterable.first(c.trackers)!; const expectedArgs: unknown[][] = []; assert.deepStrictEqual(proxy.$addTestsToRun.args, expectedArgs); @@ -414,7 +528,7 @@ suite('ExtHost Testing', () => { }); test('guards calls after runs are ended', () => { - const task = c.createTestRun('ctrl', req, 'hello world', false); + const task = c.createTestRun('ctrl', single, req, 'hello world', false); task.end(); task.setState(single.root, TestResultState.Passed); @@ -429,9 +543,9 @@ suite('ExtHost Testing', () => { test('excludes tests outside tree or explicitly excluded', () => { single.expand(single.root.id, Infinity); - const task = c.createTestRun('ctrl', { - debug: false, - tests: [single.root.children.get('id-a')!], + const task = c.createTestRun('ctrl', single, { + configuration, + include: [single.root.children.get('id-a')!], exclude: [single.root.children.get('id-a')!.children.get('id-aa')!], }, 'hello world', false); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 32f7056ef510c50a6d16549f328a107cd0e0ad33..2c3e22f143f0ab0ac7c4169e03ac28549c6e9804 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1664,6 +1664,8 @@ export class TestLocalTerminalService implements ILocalTerminalService { onPtyHostUnresponsive = Event.None; onPtyHostResponsive = Event.None; onPtyHostRestart = Event.None; + onDidMoveWindowInstance = Event.None; + onDidRequestDetach = Event.None; async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise { return new TestTerminalChildProcess(shouldPersist); @@ -1681,6 +1683,8 @@ export class TestLocalTerminalService implements ILocalTerminalService { processBinary(id: number, data: string): Promise { throw new Error('Method not implemented.'); } updateTitle(id: number, title: string): Promise { throw new Error('Method not implemented.'); } updateIcon(id: number, icon: URI | { light: URI; dark: URI } | { id: string, color?: { id: string } }, color?: string): Promise { throw new Error('Method not implemented.'); } + requestDetachInstance(workspaceId: string, instanceId: number): Promise { throw new Error('Method not implemented.'); } + acceptDetachedInstance(requestId: number, persistentProcessId: number): Promise { throw new Error('Method not implemented.'); } } class TestTerminalChildProcess implements ITerminalChildProcess { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 73a268b2b40a53d7a6d90aa84372d411042d1e1f..74a48a35472eb8c3c860ba9652eda5770415a7ca 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -292,6 +292,9 @@ import 'vs/workbench/contrib/welcome/common/newFile.contribution'; // Call Hierarchy import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; +// Type Hierarchy +import 'vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution'; + // Outline import 'vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline'; import 'vs/workbench/contrib/outline/browser/outline.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 3bf89925138e3d965e223893887113bef439741d..96c924b253aa93ec83c48cb1ea87d01c36b8939d 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -19,6 +19,7 @@ import { IProductConfiguration } from 'vs/base/common/product'; import { mark } from 'vs/base/common/performance'; import { ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials'; import { TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; +import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; interface IResourceUriProvider { (uri: URI): URI; @@ -359,6 +360,12 @@ interface IWorkbenchConstructionOptions { */ readonly resolveCommonTelemetryProperties?: ICommonTelemetryPropertiesResolver; + /** + * When provided used as the interface for sending telemetry events rather than the VS Code server. + * If no appender is provided and no server is present, no telemetry is sent. + */ + readonly telemetryAppender?: ITelemetryAppender; + /** * A set of optional commands that should be registered with the commands * registry. @@ -644,6 +651,7 @@ export { // Telemetry ICommonTelemetryPropertiesResolver, + ITelemetryAppender, // External Uris IExternalUriResolver, diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index f4231463a887ef6a7e1444975f4ed93500bd9564..1712248016860e68ab2d95fc68ed56e1de4ccfb4 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -36,7 +36,7 @@ import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.te import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test'; import { setup as setupLaunchTests } from './areas/workbench/launch.test'; -const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; }; +const tmpDir = tmp.dirSync({ name: 't' }) as { name: string; removeCallback: Function; }; const testDataPath = tmpDir.name; process.once('exit', () => { try { diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 115730256a1a0a0b064629618895c2243afd9407..bf4097f1ccbad2f063dedc4b8b182e95d851b01d 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -14,6 +14,7 @@ const MochaJUnitReporter = require('mocha-junit-reporter'); const url = require('url'); const minimatch = require('minimatch'); const playwright = require('playwright'); +const { applyReporter } = require('../reporter'); // opts const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec'; @@ -21,8 +22,8 @@ const optimist = require('optimist') // .describe('grep', 'only run tests matching ').alias('grep', 'g').alias('grep', 'f').string('grep') .describe('build', 'run with build output (out-build)').boolean('build') .describe('run', 'only run tests matching ').string('run') - .describe('glob', 'only run tests matching ').string('glob') - .describe('debug', 'do not run browsers headless').boolean('debug') + .describe('grep', 'only run tests matching ').alias('grep', 'g').alias('grep', 'f').string('grep') + .describe('debug', 'do not run browsers headless').alias('debug', ['debug-browser']).boolean('debug') .describe('browser', 'browsers in which tests should run').string('browser').default('browser', ['chromium', 'firefox', 'webkit']) .describe('reporter', 'the mocha reporter').string('reporter').default('reporter', defaultReporterName) .describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '') @@ -51,30 +52,7 @@ const withReporter = (function () { } } } else { - const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter); - let ctor; - - try { - ctor = require(reporterPath); - } catch (err) { - try { - ctor = require(argv.reporter); - } catch (err) { - ctor = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec; - console.warn(`could not load reporter: ${argv.reporter}, using ${ctor.name}`); - } - } - - function parseReporterOption(value) { - let r = /^([^=]+)=(.*)$/.exec(value); - return r ? { [r[1]]: r[2] } : {}; - } - - let reporterOptions = argv['reporter-options']; - reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions; - reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {}); - - return (_, runner) => new ctor(runner, { reporterOptions }) + return (_, runner) => applyReporter(runner, argv); } })() @@ -103,7 +81,7 @@ const testModules = (async function () { } else { // glob patterns (--glob) const defaultGlob = '**/*.test.js'; - const pattern = argv.glob || defaultGlob + const pattern = argv.run || defaultGlob isDefaultModules = pattern === defaultGlob; promise = new Promise((resolve, reject) => { @@ -183,7 +161,10 @@ async function runTestsInBrowser(testModules, browserType) { try { // @ts-expect-error - await page.evaluate(modules => loadAndRun(modules), testModules); + await page.evaluate(opts => loadAndRun(opts), { + modules: testModules, + grep: argv.grep, + }); } catch (err) { console.error(err); } @@ -235,7 +216,8 @@ class EchoRunner extends events.EventEmitter { async: runnable.async, slow: () => runnable.slow, speed: runnable.speed, - duration: runnable.duration + duration: runnable.duration, + currentRetry: () => runnable.currentRetry, }; } diff --git a/test/unit/browser/renderer.html b/test/unit/browser/renderer.html index 57d75bd21bafce5a5f5f4fe842f340830234e6d1..90d121bb038118cea0a096f77dfabeb194f3fe57 100644 --- a/test/unit/browser/renderer.html +++ b/test/unit/browser/renderer.html @@ -85,7 +85,8 @@ async: runnable.async, slow: runnable.slow(), speed: runnable.speed, - duration: runnable.duration + duration: runnable.duration, + currentRetry: runnable.currentRetry(), }; } function serializeError(err) { @@ -113,7 +114,7 @@ runner.on('pending', test => window.mocha_report('pending', serializeRunnable(test))); }; - window.loadAndRun = async function loadAndRun(modules, manual = false) { + window.loadAndRun = async function loadAndRun({ modules, grep }, manual = false) { // load await Promise.all(modules.map(module => new Promise((resolve, reject) => { require([module], resolve, err => { @@ -131,6 +132,10 @@ // run return new Promise((resolve, reject) => { + if (grep) { + mocha.grep(grep); + } + if (!manual) { mocha.reporter(PlaywrightReporter); } diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 1a707be5e38f54af4d4799b27fff63ce8a4c928f..cfddc69a310ec07b243b4316a3a834f54dba1320 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -17,7 +17,7 @@ const MochaJUnitReporter = require('mocha-junit-reporter'); const url = require('url'); const net = require('net'); const createStatsCollector = require('mocha/lib/stats-collector'); -const FullJsonStreamReporter = require('../fullJsonStreamReporter'); +const { applyReporter, importMochaReporter } = require('../reporter'); // Disable render process reuse, we still have // non-context aware native modules in the renderer. @@ -76,15 +76,6 @@ function deserializeRunnable(runnable) { }; } -function importMochaReporter(name) { - if (name === 'full-json-stream') { - return FullJsonStreamReporter; - } - - const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', name); - return require(reporterPath); -} - function deserializeError(err) { const inspect = err.inspect; err.inspect = () => inspect; @@ -125,11 +116,6 @@ class IPCRunner extends events.EventEmitter { } } -function parseReporterOption(value) { - let r = /^([^=]+)=(.*)$/.exec(value); - return r ? { [r[1]]: r[2] } : {}; -} - app.on('ready', () => { ipcMain.on('error', (_, err) => { @@ -249,23 +235,7 @@ app.on('ready', () => { }); } - let Reporter; - try { - Reporter = importMochaReporter(argv.reporter); - } catch (err) { - try { - Reporter = require(argv.reporter); - } catch (err) { - Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec; - console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`); - } - } - - let reporterOptions = argv['reporter-options']; - reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions; - reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {}); - - new Reporter(runner, { reporterOptions }); + applyReporter(runner, argv); } if (!argv.debug) { diff --git a/test/unit/reporter.js b/test/unit/reporter.js new file mode 100644 index 0000000000000000000000000000000000000000..d1630759b5d64a90865f63fc83d358db81f416f2 --- /dev/null +++ b/test/unit/reporter.js @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const mocha = require('mocha'); +const FullJsonStreamReporter = require('./fullJsonStreamReporter'); +const path = require('path'); + +function parseReporterOption(value) { + let r = /^([^=]+)=(.*)$/.exec(value); + return r ? { [r[1]]: r[2] } : {}; +} + +exports.importMochaReporter = name => { + if (name === 'full-json-stream') { + return FullJsonStreamReporter; + } + + const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', name); + return require(reporterPath); +} + +exports.applyReporter = (runner, argv) => { + let Reporter; + try { + Reporter = exports.importMochaReporter(argv.reporter); + } catch (err) { + try { + Reporter = require(argv.reporter); + } catch (err) { + Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec; + console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`); + } + } + + let reporterOptions = argv['reporter-options']; + reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions; + reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {}); + + return new Reporter(runner, { reporterOptions }); +} diff --git a/yarn.lock b/yarn.lock index ec804292bbc690b00cf85453f35edd0cfff9ca51..50bd8361428506ecf61a253779579d2805f9e468 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,6 +145,11 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@discoveryjs/json-ext@^0.5.0": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" + integrity sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g== + "@electron/get@^1.0.1": version "1.12.3" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.3.tgz#fa2723385c4b565a34c4c82f46087aa2a5fbf6d0" @@ -443,11 +448,37 @@ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/eslint@*": + version "7.2.13" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.13.tgz#e0ca7219ba5ded402062ad6f926d491ebb29dd53" + integrity sha512-LKmQCWAlnVHvvXq4oasNUMTJJb2GwSyTY8+1C7OH5ILR8mPLaljv1jxL1bXW3xB3jFbQxTKxJAvI8PyjB09aBg== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "0.0.49" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.49.tgz#3facb98ebcd4114a4ecef74e0de2175b56fd4464" + integrity sha512-K1AFuMe8a+pXmfHTtnwBvqoEylNKVeaiKYkjmcEAdytMQVJ/i9Fu7sc13GxgXdO49gkE7Hy8SyJonUZUn+eVaw== + +"@types/estree@^0.0.48": + version "0.0.48" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.48.tgz#18dc8091b285df90db2f25aa7d906cfc394b7f74" + integrity sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew== + "@types/expect@^1.20.4": version "1.20.4" resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" @@ -483,6 +514,11 @@ dependencies: "@types/node" "*" +"@types/json-schema@*": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + "@types/json-schema@^7.0.3": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" @@ -729,6 +765,14 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -738,16 +782,31 @@ "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0" +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + "@webassemblyjs/floating-point-hex-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== + "@webassemblyjs/helper-api-error@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== + "@webassemblyjs/helper-buffer@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" @@ -772,11 +831,35 @@ dependencies: "@webassemblyjs/ast" "1.9.0" +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== + "@webassemblyjs/helper-wasm-bytecode@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/helper-wasm-section@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" @@ -787,6 +870,13 @@ "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wasm-gen" "1.9.0" +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== + dependencies: + "@xtuc/ieee754" "^1.2.0" + "@webassemblyjs/ieee754@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" @@ -794,6 +884,13 @@ dependencies: "@xtuc/ieee754" "^1.2.0" +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== + dependencies: + "@xtuc/long" "4.2.2" + "@webassemblyjs/leb128@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" @@ -801,11 +898,30 @@ dependencies: "@xtuc/long" "4.2.2" +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== + "@webassemblyjs/utf8@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" + "@webassemblyjs/wasm-edit@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" @@ -820,6 +936,17 @@ "@webassemblyjs/wasm-parser" "1.9.0" "@webassemblyjs/wast-printer" "1.9.0" +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + "@webassemblyjs/wasm-gen@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" @@ -831,6 +958,16 @@ "@webassemblyjs/leb128" "1.9.0" "@webassemblyjs/utf8" "1.9.0" +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wasm-opt@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" @@ -841,6 +978,18 @@ "@webassemblyjs/wasm-gen" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + "@webassemblyjs/wasm-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" @@ -865,6 +1014,14 @@ "@webassemblyjs/helper-fsm" "1.9.0" "@xtuc/long" "4.2.2" +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@xtuc/long" "4.2.2" + "@webassemblyjs/wast-printer@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" @@ -874,6 +1031,23 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@webpack-cli/configtest@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.4.tgz#f03ce6311c0883a83d04569e2c03c6238316d2aa" + integrity sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ== + +"@webpack-cli/info@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.3.0.tgz#9d78a31101a960997a4acd41ffd9b9300627fe2b" + integrity sha512-ASiVB3t9LOKHs5DyVUcxpraBXDOKubYu/ihHhU+t1UPpxsivg6Od2E2qU4gJCekfEddzRBzHhzA/Acyw/mlK/w== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.1.tgz#b5fde2f0f79c1e120307c415a4c1d5eb15a6f278" + integrity sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -904,6 +1078,11 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== + agent-base@4: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -1378,6 +1557,11 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +available-typed-arrays@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9" + integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -1455,11 +1639,6 @@ before-after-hook@^2.1.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c" integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw== -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1663,7 +1842,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0: +browserslist@^4.0.0, browserslist@^4.14.5: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== @@ -1801,6 +1980,14 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.0" +call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -2076,6 +2263,15 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -2207,7 +2403,7 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.4" -colorette@^1.2.2: +colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -2259,6 +2455,11 @@ commander@^6.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commandpost@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.2.1.tgz#2e9c4c7508b9dc704afefaa91cab92ee6054cc68" @@ -2443,6 +2644,15 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -3039,10 +3249,10 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.737.tgz#196f2e9656f4f3c31930750e1899c091b72d36b5" integrity sha512-P/B84AgUSQXaum7a8m11HUsYL8tj9h/Pt5f7Hg7Ty6bm5DxlFq+e5+ouHUoNQMsKDJ7u4yGfI8mOErCmSH9wyg== -electron@12.0.13: - version "12.0.13" - resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.13.tgz#f1c7f3d8b25078d7622ed900cc8f2aa4731a5e0a" - integrity sha512-2yx102mhqgyaTVH+GCet4hURKOIuI27DcVdlval5T3eOEZMOHNrCbi8/8PZ7MqMfB5PIxJ+grS5S00/n5Zcu2Q== +electron@13.1.6: + version "13.1.6" + resolved "https://registry.yarnpkg.com/electron/-/electron-13.1.6.tgz#6ecaf969255d62ce82cc0b5c948bf26e7dfb489b" + integrity sha512-XiB55/JTaQpDFQrD9pulYnOGwaWeMyRIub5ispvoE2bWBvM5zVMLptwMLb0m3KTMrfSkzhedZvOu7fwYvR7L7Q== dependencies: "@electron/get" "^1.0.1" "@types/node" "^14.6.2" @@ -3078,11 +3288,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3100,16 +3305,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -enhanced-resolve@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - -enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: +enhanced-resolve@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== @@ -3118,6 +3314,14 @@ enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.0: + version "5.8.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" + integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -3133,6 +3337,11 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== +envinfo@^7.7.3: + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + errno@^0.1.3, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -3182,6 +3391,33 @@ es-abstract@^1.18.0-next.1: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-abstract@^1.18.0-next.2: + version "1.18.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" + integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.10.3" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.6.0.tgz#e72ab05b7412e62b9be37c37a09bdb6000d706f0" + integrity sha512-f8kcHX1ArhllUtb/wVSyvygoKCznIjnxhLxy7TCvIiMdT7fL4ZDTIKaadMe6eLvOXg6Wk02UeoFgUoZ2EKZZUA== + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -3283,6 +3519,14 @@ eslint-plugin-jsdoc@^19.1.0: semver "^6.3.0" spdx-expression-parse "^3.0.0" +eslint-scope@5.1.1, eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -3291,14 +3535,6 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - eslint-utils@^1.3.1, eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -3494,6 +3730,11 @@ events@^3.0.0: resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -3515,6 +3756,21 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -3708,6 +3964,11 @@ fast-plist@0.1.2: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + fastq@^1.6.0: version "1.9.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947" @@ -3968,6 +4229,11 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -4135,6 +4401,15 @@ get-intrinsic@^1.0.0: has "^1.0.3" has-symbols "^1.0.1" +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -4149,6 +4424,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-uri@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" @@ -4238,6 +4518,11 @@ glob-stream@^6.1.0: to-absolute-glob "^2.0.0" unique-stream "^2.0.2" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob-watcher@^5.0.3: version "5.0.5" resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.5.tgz#aa6bce648332924d9a8489be41e3e5c52d4186dc" @@ -4308,13 +4593,6 @@ global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -4326,15 +4604,6 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - global-tunnel-ng@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" @@ -4726,6 +4995,11 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -4741,6 +5015,11 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -4919,6 +5198,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + husky@^0.13.1: version "0.13.4" resolved "https://registry.yarnpkg.com/husky/-/husky-0.13.4.tgz#48785c5028de3452a51c48c12c4f94b2124a1407" @@ -5003,13 +5287,13 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" imurmurhash@^0.1.4: version "0.1.4" @@ -5054,7 +5338,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -5107,6 +5391,11 @@ interpret@^1.4.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" @@ -5149,6 +5438,13 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arguments@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5159,6 +5455,11 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-bigint@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" + integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -5173,6 +5474,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" + integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + dependencies: + call-bind "^1.0.2" + is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -5183,6 +5491,11 @@ is-callable@^1.1.4, is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + is-ci@^1.0.9: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -5209,6 +5522,13 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -5302,6 +5622,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.9.tgz#e5f82c2323673e7fcad3d12858c83c4039f6399c" + integrity sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A== + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -5328,11 +5653,16 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.0: +is-negative-zero@^2.0.0, is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +is-number-object@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" + integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -5406,6 +5736,14 @@ is-regex@^1.1.1: dependencies: has-symbols "^1.0.1" +is-regex@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.2" + is-relative@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -5423,6 +5761,16 @@ is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== + is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -5430,6 +5778,24 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" +is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.3: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" + integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== + dependencies: + available-typed-arrays "^1.0.2" + call-bind "^1.0.2" + es-abstract "^1.18.0-next.2" + foreach "^2.0.5" + has-symbols "^1.0.1" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -5556,6 +5922,15 @@ istextorbinary@1.0.2: binaryextensions "~1.0.0" textextensions "~1.0.0" +jest-worker@^27.0.2: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" + integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jpeg-js@^0.4.2: version "0.4.3" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" @@ -5665,11 +6040,6 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -5856,16 +6226,12 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" - integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -6158,7 +6524,7 @@ memoizee@0.4.X: next-tick "^1.1.0" timers-ext "^0.1.7" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -6193,6 +6559,11 @@ merge-stream@^1.0.0: dependencies: readable-stream "^2.0.1" +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -6257,6 +6628,11 @@ mime-db@1.45.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.28" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" @@ -6264,6 +6640,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.45.0" +mime-types@^2.1.27: + version "2.1.31" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + dependencies: + mime-db "1.48.0" + mime@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -6552,7 +6935,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -neo-async@^2.5.0, neo-async@^2.6.1: +neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== @@ -6736,6 +7119,13 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -6789,6 +7179,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + object-inspect@^1.8.0: version "1.9.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" @@ -6811,7 +7206,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1: +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== @@ -6900,7 +7295,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -7021,7 +7416,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -7199,6 +7594,11 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -7316,7 +7716,7 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-dir@^4.1.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -8089,6 +8489,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" @@ -8255,12 +8662,12 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: - resolve-from "^3.0.0" + resolve-from "^5.0.0" resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" @@ -8280,6 +8687,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-options@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" @@ -8307,6 +8719,14 @@ resolve@^1.3.2: dependencies: path-parse "^1.0.6" +resolve@^1.9.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -8515,6 +8935,13 @@ semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -8536,6 +8963,13 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -8564,6 +8998,13 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -8571,11 +9012,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shell-quote@^1.6.1: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" @@ -8591,7 +9044,7 @@ sigmund@^1.0.1: resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -8700,7 +9153,7 @@ socks@^2.3.3: ip "^1.1.5" smart-buffer "^4.1.0" -source-list-map@^2.0.0: +source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== @@ -8731,7 +9184,7 @@ source-map-support@^0.3.2: dependencies: source-map "0.1.32" -source-map-support@~0.5.12: +source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -8761,7 +9214,7 @@ source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -9019,6 +9472,14 @@ string.prototype.trimend@^1.0.1: call-bind "^1.0.0" define-properties "^1.1.3" +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string.prototype.trimstart@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" @@ -9027,6 +9488,14 @@ string.prototype.trimstart@^1.0.1: call-bind "^1.0.0" define-properties "^1.1.3" +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -9104,6 +9573,11 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@3.1.1, strip-json-comments@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -9162,7 +9636,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -9183,6 +9657,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + sver-compat@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" @@ -9225,6 +9706,11 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + tar-fs@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -9294,6 +9780,18 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" +terser-webpack-plugin@^5.1.3: + version "5.1.4" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz#c369cf8a47aa9922bd0d8a94fe3d3da11a7678a1" + integrity sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA== + dependencies: + jest-worker "^27.0.2" + p-limit "^3.1.0" + schema-utils "^3.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + terser "^5.7.0" + terser@^4.1.2: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" @@ -9303,6 +9801,15 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784" + integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -9511,16 +10018,15 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" -ts-loader@^6.2.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.2.tgz#dffa3879b01a1a1e0a4b85e2b8421dc0dfff1c58" - integrity sha512-HDo5kXZCBml3EUPcc7RlZOV/JGlLHwppTLEHb3SHnr5V7NXD4klMEkrhJe5wgRbaWsSXi+Y1SIBN/K9B6zWGWQ== +ts-loader@^9.2.3: + version "9.2.3" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.3.tgz#dc3b6362a4d4382493cd4f138d345f419656de68" + integrity sha512-sEyWiU3JMHBL55CIeC4iqJQadI0U70A5af0kvgbNLHVNz2ACztQg0j/9x10bjjIht8WfFYLKfn4L6tkZ+pu+8Q== dependencies: - chalk "^2.3.0" - enhanced-resolve "^4.0.0" - loader-utils "^1.0.2" + chalk "^4.1.0" + enhanced-resolve "^5.0.0" micromatch "^4.0.0" - semver "^6.0.0" + semver "^7.3.4" ts-morph@^11.0.0: version "11.0.0" @@ -9632,16 +10138,26 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.4.0-dev.20210708: - version "4.4.0-dev.20210708" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-dev.20210708.tgz#0043aa6d3b81c111c6215477a31774b5a864e7e1" - integrity sha512-jGNamsvrU8F8KjMawCauI7bQeUPKYdyIp4yiEsKv8Uk1gt494FN09wgtH9wbbT0qK7a7lel7A/N/DodpPWK/6Q== +typescript@^4.4.0-dev.20210713: + version "4.4.0-dev.20210713" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-dev.20210713.tgz#80e5d4c550e464e6aad8c0e2aa4cf42d67ce549d" + integrity sha512-Z8zYMhAwLa7mpc3LP3Z+fOTmXsRqkvgepB+dEg08g9tI9B+kR0izmPZOlcfBN7GlyxmZe5qpnnGmkhxZeHxa3g== typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -9809,6 +10325,18 @@ util@^0.11.0: dependencies: inherits "2.0.3" +util@^0.12.4: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" + integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" + uuid@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" @@ -9819,11 +10347,16 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: +v8-compile-cache@^2.0.3: version "2.2.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== +v8-compile-cache@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + v8-inspect-profiler@^0.0.22: version "0.0.22" resolved "https://registry.yarnpkg.com/v8-inspect-profiler/-/v8-inspect-profiler-0.0.22.tgz#34d3ba35a965c437ed28279d31cd42d7698a4002" @@ -10004,10 +10537,10 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vscode-debugprotocol@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.47.0.tgz#700055bea38633a9530a5a552fb3ea314d76b73f" - integrity sha512-ii7oCz3Wfr/SGtFr5AYop5dJm0dUmpg0hq2lTzTBdaht8nSheYMMjPntxULBR+2TUxXLcCKFZkF2UEJQduYsIQ== +vscode-debugprotocol@1.48.0-pre.0: + version "1.48.0-pre.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.48.0-pre.0.tgz#954e6a034da879d39ed5ef749a063d15eba14f10" + integrity sha512-iPaDd/5CWaYfOdfaMALvCADfWAU9j563IpvxXryg09hx9Cy7ydsulvbdRAini6tTKXdYndNBZgDIJ3PmlJoRgQ== vscode-nls-dev@^3.3.1: version "3.3.1" @@ -10119,22 +10652,40 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" -webpack-cli@^3.3.12: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== +watchpack@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.2.0.tgz#47d78f5415fe550ecd740f99fe2882323a58b1ce" + integrity sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA== dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-cli@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.7.2.tgz#a718db600de6d3906a4357e059ae584a89f4c1a5" + integrity sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.4" + "@webpack-cli/info" "^1.3.0" + "@webpack-cli/serve" "^1.5.1" + colorette "^1.2.1" + commander "^7.0.0" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + v8-compile-cache "^2.2.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.8.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" + integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" @@ -10144,22 +10695,30 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-stream@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/webpack-stream/-/webpack-stream-5.2.1.tgz#35c992161399fe8cad9c10d4a5c258f022629b39" - integrity sha512-WvyVU0K1/VB1NZ7JfsaemVdG0PXAQUqbjUNW4A58th4pULvKMQxG+y33HXTL02JvD56ko2Cub+E2NyPwrLBT/A== +webpack-sources@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.0.tgz#9ed2de69b25143a4c18847586ad9eccb19278cfa" + integrity sha512-WyOdtwSvOML1kbgtXbTDnEW0jkJ7hZr/bDByIwszhWd/4XX1A3XMkrbFMsuH4+/MfLlZCUzlAdg4r7jaGKEIgQ== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack-stream@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/webpack-stream/-/webpack-stream-6.1.2.tgz#ee90bc07d0ff937239d75ed22aa728072c9e7ee1" + integrity sha512-Bpbsrix1cmWRN705JEg69ErgNAEOpQBvtuWKFW3ZCrLddoPPK6oVpQn4svxNdfedqMLlWA3GLOLvw4c7u63GqA== dependencies: fancy-log "^1.3.3" lodash.clone "^4.3.2" lodash.some "^4.2.2" - memory-fs "^0.4.1" + memory-fs "^0.5.0" plugin-error "^1.0.1" - supports-color "^5.5.0" + supports-color "^7.2.0" through "^2.3.8" vinyl "^2.1.0" webpack "^4.26.1" -webpack@^4.26.1, webpack@^4.43.0: +webpack@^4.26.1: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== @@ -10188,11 +10747,51 @@ webpack@^4.26.1, webpack@^4.43.0: watchpack "^1.7.4" webpack-sources "^1.4.1" +webpack@^5.42.0: + version "5.42.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.42.0.tgz#39aadbce84ad2cebf86cc5f88a2c53db65cbddfb" + integrity sha512-Ln8HL0F831t1x/yPB/qZEUVmZM4w9BnHZ1EQD/sAUHv8m22hthoPniWTXEzFMh/Sf84mhrahut22TX5KxWGuyQ== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.48" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + acorn "^8.4.1" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.8.0" + es-module-lexer "^0.6.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.2.0" + webpack-sources "^2.3.0" + when@^3.7.7: version "3.7.8" resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" integrity sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -10203,14 +10802,27 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@2.0.2: +which-typed-array@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" + integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== + dependencies: + available-typed-arrays "^1.0.2" + call-bind "^1.0.0" + es-abstract "^1.18.0-next.1" + foreach "^2.0.5" + function-bind "^1.1.1" + has-symbols "^1.0.1" + is-typed-array "^1.1.3" + +which@2.0.2, which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -10224,6 +10836,11 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + windows-foreground-love@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/windows-foreground-love/-/windows-foreground-love-0.4.0.tgz#79b628ba0ffc0436fa8066da8f85db042e431976" @@ -10476,7 +11093,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@13.3.2, yargs@^13.3.2: +yargs@13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==