diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 3d1e15e5d55..19a85b4692d 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -99,7 +99,7 @@ Next, let's try debugging. 2. Go to your local VS Code client, and use the **Run / Debug** view to launch the **VS Code** configuration. (Typically the default, so you can likely just press F5). - > **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../../.vscode/launch.json). However, running `./scripts/code.sh` first will set up Electron which will usually solve timeout issues. + > **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../.vscode/launch.json). However, running `./scripts/code.sh` first will set up Electron which will usually solve timeout issues. 3. After a bit, Code - OSS will appear with the debugger attached! diff --git a/.eslint-plugin-local/code-import-patterns.ts b/.eslint-plugin-local/code-import-patterns.ts index ed14fa35d5a..e4beb9a4738 100644 --- a/.eslint-plugin-local/code-import-patterns.ts +++ b/.eslint-plugin-local/code-import-patterns.ts @@ -18,7 +18,7 @@ interface ConditionalPattern { interface RawImportPatternsConfig { target: string; - layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-utility' | 'electron-main'; + layer?: 'common' | 'worker' | 'browser' | 'electron-browser' | 'node' | 'electron-utility' | 'electron-main'; test?: boolean; restrictions: string | (string | ConditionalPattern)[]; } @@ -80,7 +80,7 @@ export = new class implements eslint.Rule.RuleModule { return this._optionsCache.get(options)!; } - type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-utility' | 'electron-main'; + type Layer = 'common' | 'worker' | 'browser' | 'electron-browser' | 'node' | 'electron-utility' | 'electron-main'; interface ILayerRule { layer: Layer; @@ -98,7 +98,7 @@ export = new class implements eslint.Rule.RuleModule { { layer: 'common', deps: orSegment(['common']) }, { layer: 'worker', deps: orSegment(['common', 'worker']) }, { layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true }, - { layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true }, + { layer: 'electron-browser', deps: orSegment(['common', 'browser', 'electron-browser']), isBrowser: true }, { layer: 'node', deps: orSegment(['common', 'node']), isNode: true }, { layer: 'electron-utility', deps: orSegment(['common', 'node', 'electron-utility']), isNode: true, isElectron: true }, { layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-utility', 'electron-main']), isNode: true, isElectron: true }, diff --git a/.github/commands.json b/.github/commands.json index 38da97915a2..4ad5b2cb6bd 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -115,7 +115,7 @@ "type": "label", "name": "*duplicate", "action": "close", - "reason": "not_planned", + "reason": "duplicate", "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { @@ -538,5 +538,62 @@ "addLabel": "info-needed", "removeLabel": "~confirmation-needed", "comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "~chat-rate-limiting", + "removeLabel": "~chat-rate-limiting", + "action": "close", + "reason": "not_planned", + "comment": "This issue is a duplicate of https://github.com/microsoft/vscode-copilot-release/issues/2627. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." + }, + { + "type": "label", + "name": "~chat-request-failed", + "removeLabel": "~chat-request-failed", + "action": "close", + "reason": "not_planned", + "comment": "This issue is a duplicate of https://github.com/microsoft/vscode-copilot-release/issues/4332. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." + }, + { + "type": "label", + "name": "~chat-rai-content-filters", + "removeLabel": "~chat-rai-content-filters", + "action": "close", + "reason": "not_planned", + "comment": "This issue is a duplicate of https://github.com/microsoft/vscode-copilot-release/issues/2625. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." + }, + { + "type": "label", + "name": "~chat-public-code-blocking", + "removeLabel": "~chat-public-code-blocking", + "action": "close", + "reason": "not_planned", + "comment": "This issue is a duplicate of https://github.com/microsoft/vscode-copilot-release/issues/2626. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." + }, + { + "type": "label", + "name": "~chat-lm-unavailable", + "removeLabel": "~chat-lm-unavailable", + "action": "close", + "reason": "not_planned", + "comment": "This issue is a duplicate of https://github.com/microsoft/vscode-copilot-release/issues/2116. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." + }, + { + "type": "label", + "name": "~chat-authentication", + "removeLabel": "~chat-authentication", + "action": "close", + "reason": "not_planned", + "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode-copilot-release/issues/11078, if the bug you are experiencing is not there, please comment on this closed issue thread so we can re-open it.", + "assign": ["TylerLeonhardt"] + }, + { + "type": "label", + "name": "~chat-no-response-returned", + "removeLabel": "~chat-no-response-returned", + "action": "close", + "reason": "not_planned", + "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode-copilot-release/issues/7034. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." } ] diff --git a/.npmrc b/.npmrc index 98fd4adc9e0..3d79d10ff8f 100644 --- a/.npmrc +++ b/.npmrc @@ -1,7 +1,8 @@ disturl="https://electronjs.org/headers" target="35.5.1" -ms_build_id="11708675" +ms_build_id="11727614" runtime="electron" build_from_source="true" legacy-peer-deps="true" timeout=180000 +npm_config_node_gyp="node build/npm/gyp/node_modules/node-gyp/bin/node-gyp.js" diff --git a/.vscode/launch.json b/.vscode/launch.json index f922b5d9c80..6b95eed617f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -254,6 +254,7 @@ "VSCODE_SKIP_PRELAUNCH": "1", }, "cleanUp": "wholeBrowser", + "killBehavior": "polite", "runtimeArgs": [ "--inspect-brk=5875", "--no-cached-data", diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index cc9420281ed..ed5ae1cf8a2 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"May 2025\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"June 2025\"" }, { "kind": 1, diff --git a/build/.cachesalt b/build/.cachesalt index 2d02e2ba768..a3213b850c3 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2025-05-14T16:33:46.494Z +2025-06-09T07:16:15.626Z diff --git a/build/azure-pipelines/win32/codesign.js b/build/azure-pipelines/win32/codesign.js index 630f9a64ba1..438ae9b474c 100644 --- a/build/azure-pipelines/win32/codesign.js +++ b/build/azure-pipelines/win32/codesign.js @@ -17,7 +17,7 @@ async function main() { // 2. Codesign Powershell scripts // 3. Codesign context menu appx package (insiders only) const codesignTask1 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows', codeSigningFolderPath, '*.dll,*.exe,*.node'); - const codesignTask2 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.ps1'); + const codesignTask2 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.ps1,*.psm1'); const codesignTask3 = process.env['VSCODE_QUALITY'] === 'insider' ? (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.appx') : undefined; diff --git a/build/azure-pipelines/win32/codesign.ts b/build/azure-pipelines/win32/codesign.ts index 7e7170709b5..6f3a6bdee02 100644 --- a/build/azure-pipelines/win32/codesign.ts +++ b/build/azure-pipelines/win32/codesign.ts @@ -19,7 +19,7 @@ async function main() { // 2. Codesign Powershell scripts // 3. Codesign context menu appx package (insiders only) const codesignTask1 = spawnCodesignProcess(esrpCliDLLPath, 'sign-windows', codeSigningFolderPath, '*.dll,*.exe,*.node'); - const codesignTask2 = spawnCodesignProcess(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.ps1'); + const codesignTask2 = spawnCodesignProcess(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.ps1,*.psm1'); const codesignTask3 = process.env['VSCODE_QUALITY'] === 'insider' ? spawnCodesignProcess(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.appx') : undefined; diff --git a/build/buildfile.js b/build/buildfile.js index b1ce1186d4e..b5e8f6e7e6c 100644 --- a/build/buildfile.js +++ b/build/buildfile.js @@ -18,7 +18,7 @@ exports.workerExtensionHost = createModuleDescription('vs/workbench/api/worker/e exports.workerNotebook = createModuleDescription('vs/workbench/contrib/notebook/common/services/notebookWebWorkerMain'); exports.workerLanguageDetection = createModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionWebWorkerMain'); exports.workerLocalFileSearch = createModuleDescription('vs/workbench/services/search/worker/localFileSearchMain'); -exports.workerProfileAnalysis = createModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorkerMain'); +exports.workerProfileAnalysis = createModuleDescription('vs/platform/profiling/electron-browser/profileAnalysisWorkerMain'); exports.workerOutputLinks = createModuleDescription('vs/workbench/contrib/output/common/outputLinkComputerMain'); exports.workerBackgroundTokenization = createModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.workerMain'); @@ -43,7 +43,7 @@ exports.code = [ // 'vs/code/node/cli' is not included here because it comes in via ./src/cli.js createModuleDescription('vs/code/node/cliProcessMain'), createModuleDescription('vs/code/electron-utility/sharedProcess/sharedProcessMain'), - createModuleDescription('vs/code/electron-sandbox/workbench/workbench'), + createModuleDescription('vs/code/electron-browser/workbench/workbench'), createModuleDescription('vs/workbench/contrib/webview/browser/pre/service-worker') ]; diff --git a/build/checker/layersChecker.js b/build/checker/layersChecker.js new file mode 100644 index 00000000000..b2e319b5ecb --- /dev/null +++ b/build/checker/layersChecker.js @@ -0,0 +1,136 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const typescript_1 = __importDefault(require("typescript")); +const fs_1 = require("fs"); +const path_1 = require("path"); +const minimatch_1 = require("minimatch"); +// +// ############################################################################################# +// +// A custom typescript checker for the specific task of detecting the use of certain types in a +// layer that does not allow such use. +// +// Make changes to below RULES to lift certain files from these checks only if absolutely needed +// +// NOTE: Most layer checks are done via tsconfig..json files. +// +// ############################################################################################# +// +// Types that are defined in a common layer but are known to be only +// available in native environments should not be allowed in browser +const NATIVE_TYPES = [ + 'NativeParsedArgs', + 'INativeEnvironmentService', + 'AbstractNativeEnvironmentService', + 'INativeWindowConfiguration', + 'ICommonNativeHostService', + 'INativeHostService', + 'IMainProcessService', + 'INativeBrowserElementsService', +]; +const RULES = [ + // Tests: skip + { + target: '**/vs/**/test/**', + skip: true // -> skip all test files + }, + // Common: vs/platform services that can access native types + { + target: `**/vs/platform/{${[ + 'environment/common/*.ts', + 'window/common/window.ts', + 'native/common/native.ts', + 'native/common/nativeHostService.ts', + 'browserElements/common/browserElements.ts', + 'browserElements/common/nativeBrowserElementsService.ts' + ].join(',')}}`, + disallowedTypes: [ /* Ignore native types that are defined from here */], + }, + // Common: vs/base/parts/sandbox/electron-browser/preload{,-aux}.ts + { + target: '**/vs/base/parts/sandbox/electron-browser/preload{,-aux}.ts', + disallowedTypes: NATIVE_TYPES, + }, + // Common + { + target: '**/vs/**/common/**', + disallowedTypes: NATIVE_TYPES, + }, + // Common + { + target: '**/vs/**/worker/**', + disallowedTypes: NATIVE_TYPES, + }, + // Browser + { + target: '**/vs/**/browser/**', + disallowedTypes: NATIVE_TYPES, + }, + // Electron (main, utility) + { + target: '**/vs/**/{electron-main,electron-utility}/**', + disallowedTypes: [ + 'ipcMain' // not allowed, use validatedIpcMain instead + ] + } +]; +const TS_CONFIG_PATH = (0, path_1.join)(__dirname, '../../', 'src', 'tsconfig.json'); +let hasErrors = false; +function checkFile(program, sourceFile, rule) { + checkNode(sourceFile); + function checkNode(node) { + if (node.kind !== typescript_1.default.SyntaxKind.Identifier) { + return typescript_1.default.forEachChild(node, checkNode); // recurse down + } + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (!symbol) { + return; + } + let text = symbol.getName(); + let _parentSymbol = symbol; + while (_parentSymbol.parent) { + _parentSymbol = _parentSymbol.parent; + } + const parentSymbol = _parentSymbol; + text = parentSymbol.getName(); + if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/checker/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); + hasErrors = true; + return; + } + } +} +function createProgram(tsconfigPath) { + const tsConfig = typescript_1.default.readConfigFile(tsconfigPath, typescript_1.default.sys.readFile); + const configHostParser = { fileExists: fs_1.existsSync, readDirectory: typescript_1.default.sys.readDirectory, readFile: file => (0, fs_1.readFileSync)(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; + const tsConfigParsed = typescript_1.default.parseJsonConfigFileContent(tsConfig.config, configHostParser, (0, path_1.resolve)((0, path_1.dirname)(tsconfigPath)), { noEmit: true }); + const compilerHost = typescript_1.default.createCompilerHost(tsConfigParsed.options, true); + return typescript_1.default.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); +} +// +// Create program and start checking +// +const program = createProgram(TS_CONFIG_PATH); +for (const sourceFile of program.getSourceFiles()) { + for (const rule of RULES) { + if ((0, minimatch_1.match)([sourceFile.fileName], rule.target).length > 0) { + if (!rule.skip) { + checkFile(program, sourceFile, rule); + } + break; + } + } +} +if (hasErrors) { + process.exit(1); +} +//# sourceMappingURL=layersChecker.js.map \ No newline at end of file diff --git a/build/checker/layersChecker.ts b/build/checker/layersChecker.ts new file mode 100644 index 00000000000..68e12e61c40 --- /dev/null +++ b/build/checker/layersChecker.ts @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import ts from 'typescript'; +import { readFileSync, existsSync } from 'fs'; +import { resolve, dirname, join } from 'path'; +import { match } from 'minimatch'; + +// +// ############################################################################################# +// +// A custom typescript checker for the specific task of detecting the use of certain types in a +// layer that does not allow such use. +// +// Make changes to below RULES to lift certain files from these checks only if absolutely needed +// +// NOTE: Most layer checks are done via tsconfig..json files. +// +// ############################################################################################# +// + +// Types that are defined in a common layer but are known to be only +// available in native environments should not be allowed in browser +const NATIVE_TYPES = [ + 'NativeParsedArgs', + 'INativeEnvironmentService', + 'AbstractNativeEnvironmentService', + 'INativeWindowConfiguration', + 'ICommonNativeHostService', + 'INativeHostService', + 'IMainProcessService', + 'INativeBrowserElementsService', +]; + +const RULES: IRule[] = [ + + // Tests: skip + { + target: '**/vs/**/test/**', + skip: true // -> skip all test files + }, + + // Common: vs/platform services that can access native types + { + target: `**/vs/platform/{${[ + 'environment/common/*.ts', + 'window/common/window.ts', + 'native/common/native.ts', + 'native/common/nativeHostService.ts', + 'browserElements/common/browserElements.ts', + 'browserElements/common/nativeBrowserElementsService.ts' + ].join(',')}}`, + disallowedTypes: [/* Ignore native types that are defined from here */], + }, + + // Common: vs/base/parts/sandbox/electron-browser/preload{,-aux}.ts + { + target: '**/vs/base/parts/sandbox/electron-browser/preload{,-aux}.ts', + disallowedTypes: NATIVE_TYPES, + }, + + // Common + { + target: '**/vs/**/common/**', + disallowedTypes: NATIVE_TYPES, + }, + + // Common + { + target: '**/vs/**/worker/**', + disallowedTypes: NATIVE_TYPES, + }, + + // Browser + { + target: '**/vs/**/browser/**', + disallowedTypes: NATIVE_TYPES, + }, + + // Electron (main, utility) + { + target: '**/vs/**/{electron-main,electron-utility}/**', + disallowedTypes: [ + 'ipcMain' // not allowed, use validatedIpcMain instead + ] + } +]; + +const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json'); + +interface IRule { + target: string; + skip?: boolean; + disallowedTypes?: string[]; +} + +let hasErrors = false; + +function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) { + checkNode(sourceFile); + + function checkNode(node: ts.Node): void { + if (node.kind !== ts.SyntaxKind.Identifier) { + return ts.forEachChild(node, checkNode); // recurse down + } + + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + + if (!symbol) { + return; + } + + let text = symbol.getName(); + let _parentSymbol: any = symbol; + + while (_parentSymbol.parent) { + _parentSymbol = _parentSymbol.parent; + } + + const parentSymbol = _parentSymbol as ts.Symbol; + text = parentSymbol.getName(); + + if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/checker/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); + + hasErrors = true; + return; + } + } +} + +function createProgram(tsconfigPath: string): ts.Program { + const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); + + const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; + const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true }); + + const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); + + return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); +} + +// +// Create program and start checking +// +const program = createProgram(TS_CONFIG_PATH); + +for (const sourceFile of program.getSourceFiles()) { + for (const rule of RULES) { + if (match([sourceFile.fileName], rule.target).length > 0) { + if (!rule.skip) { + checkFile(program, sourceFile, rule); + } + + break; + } + } +} + +if (hasErrors) { + process.exit(1); +} diff --git a/build/checker/tsconfig.browser.json b/build/checker/tsconfig.browser.json new file mode 100644 index 00000000000..67868ef7575 --- /dev/null +++ b/build/checker/tsconfig.browser.json @@ -0,0 +1,29 @@ +{ + "extends": "../../src/tsconfig.base.json", + "compilerOptions": { + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "types": [], + "noEmit": true, + "skipLibCheck": true + }, + "include": [ + "../../src/*.ts", + "../../src/**/common/**/*.ts", + "../../src/**/browser/**/*.ts", + "../../src/typings/*.d.ts", + "../../src/vs/monaco.d.ts", + "../../src/vscode-dts/vscode.proposed.*.d.ts", + "../../src/vscode-dts/vscode.d.ts", + "../../node_modules/@webgpu/types/dist/index.d.ts", + "../../node_modules/@types/trusted-types/index.d.ts", + "../../node_modules/@types/wicg-file-system-access/index.d.ts" + ], + "exclude": [ + "../../src/**/test/**", + "../../src/**/fixtures/**" + ] +} diff --git a/build/checker/tsconfig.electron-browser.json b/build/checker/tsconfig.electron-browser.json new file mode 100644 index 00000000000..2cbe3d3bd33 --- /dev/null +++ b/build/checker/tsconfig.electron-browser.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.browser.json", + "include": [ + "../../src/**/common/**/*.ts", + "../../src/**/browser/**/*.ts", + "../../src/**/electron-browser/**/*.ts", + "../../src/typings/*.d.ts", + "../../src/vs/monaco.d.ts", + "../../src/vscode-dts/vscode.proposed.*.d.ts", + "../../src/vscode-dts/vscode.d.ts", + "../../node_modules/@webgpu/types/dist/index.d.ts", + "../../node_modules/@types/trusted-types/index.d.ts", + "../../node_modules/@types/wicg-file-system-access/index.d.ts" + ], + "exclude": [ + "../../src/**/test/**", + "../../src/**/fixtures/**", + "../../src/vs/base/parts/sandbox/electron-browser/preload.ts", // Preload scripts for Electron sandbox + "../../src/vs/base/parts/sandbox/electron-browser/preload-aux.ts" // have limited access to node.js APIs + ] +} diff --git a/build/checker/tsconfig.electron-main.json b/build/checker/tsconfig.electron-main.json new file mode 100644 index 00000000000..a275f65d9e5 --- /dev/null +++ b/build/checker/tsconfig.electron-main.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.node.json", + "include": [ + "../../src/**/common/**/*.ts", + "../../src/**/node/**/*.ts", + "../../src/**/electron-main/**/*.ts", + "../../src/typings/*.d.ts", + "../../src/vs/monaco.d.ts", + "../../src/vscode-dts/vscode.proposed.*.d.ts", + "../../src/vscode-dts/vscode.d.ts", + "../../node_modules/@types/trusted-types/index.d.ts", + ], + "exclude": [ + "../../src/**/test/**", + "../../src/**/fixtures/**", + ] +} diff --git a/build/checker/tsconfig.electron-utility.json b/build/checker/tsconfig.electron-utility.json new file mode 100644 index 00000000000..25b33be427d --- /dev/null +++ b/build/checker/tsconfig.electron-utility.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.node.json", + "include": [ + "../../src/**/common/**/*.ts", + "../../src/**/node/**/*.ts", + "../../src/**/electron-utility/**/*.ts", + "../../src/typings/*.d.ts", + "../../src/vs/monaco.d.ts", + "../../src/vscode-dts/vscode.proposed.*.d.ts", + "../../src/vscode-dts/vscode.d.ts", + "../../node_modules/@types/trusted-types/index.d.ts", + ], + "exclude": [ + "../../src/**/test/**", + "../../src/**/fixtures/**", + ] +} diff --git a/build/checker/tsconfig.node.json b/build/checker/tsconfig.node.json new file mode 100644 index 00000000000..2e483136e08 --- /dev/null +++ b/build/checker/tsconfig.node.json @@ -0,0 +1,27 @@ +{ + "extends": "../../src/tsconfig.base.json", + "compilerOptions": { + "lib": [ + "ES2022" + ], + "types": [ + "node" + ], + "noEmit": true, + "skipLibCheck": true + }, + "include": [ + "../../src/*.ts", + "../../src/**/common/**/*.ts", + "../../src/**/node/**/*.ts", + "../../src/typings/*.d.ts", + "../../src/vs/monaco.d.ts", + "../../src/vscode-dts/vscode.proposed.*.d.ts", + "../../src/vscode-dts/vscode.d.ts", + "../../node_modules/@types/trusted-types/index.d.ts", + ], + "exclude": [ + "../../src/**/test/**", + "../../src/**/fixtures/**" + ] +} diff --git a/build/checker/tsconfig.worker.json b/build/checker/tsconfig.worker.json new file mode 100644 index 00000000000..ebb919bf0f2 --- /dev/null +++ b/build/checker/tsconfig.worker.json @@ -0,0 +1,28 @@ +{ + "extends": "../../src/tsconfig.base.json", + "compilerOptions": { + "lib": [ + "ES2022", + "WebWorker", + "Webworker.Iterable", + "WebWorker.AsyncIterable" + ], + "types": [], + "noEmit": true, + "skipLibCheck": true + }, + "include": [ + "../../src/**/common/**/*.ts", + "../../src/**/worker/**/*.ts", + "../../src/typings/*.d.ts", + "../../src/vs/monaco.d.ts", + "../../src/vscode-dts/vscode.proposed.*.d.ts", + "../../src/vscode-dts/vscode.d.ts", + "../../node_modules/@types/trusted-types/index.d.ts", + "../../node_modules/@types/wicg-file-system-access/index.d.ts" + ], + "exclude": [ + "../../src/**/test/**", + "../../src/**/fixtures/**" + ] +} diff --git a/build/filters.js b/build/filters.js index 4f18bf6607a..035bd3ddb91 100644 --- a/build/filters.js +++ b/build/filters.js @@ -103,6 +103,7 @@ module.exports.indentationFilter = [ '!extensions/vscode-api-tests/testWorkspace2/**', '!build/monaco/**', '!build/win32/**', + '!build/checker/**', // except multiple specific files '!**/package.json', diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index c1d64c00338..fa850962f5f 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -67,9 +67,7 @@ const serverResourceIncludes = [ 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', // Terminal shell integration - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/GitTabExpansion.psm1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', @@ -80,7 +78,7 @@ const serverResourceIncludes = [ ]; const serverResourceExcludes = [ - '!out-build/vs/**/{electron-sandbox,electron-main,electron-utility}/**', + '!out-build/vs/**/{electron-browser,electron-main,electron-utility}/**', '!out-build/vs/editor/standalone/**', '!out-build/vs/workbench/**/*-tb.png', '!**/test/**' @@ -257,7 +255,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa const src = gulp.src(sourceFolderName + '/**', { base: '.' }) .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })) .pipe(util.setExecutableBit(['**/*.sh'])) - .pipe(filter(['**', '!**/*.js.map'])); + .pipe(filter(['**', '!**/*.{js,css}.map'])); const workspaceExtensionPoints = ['debuggers', 'jsonValidation']; const isUIExtension = (manifest) => { @@ -298,7 +296,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa const extensions = gulp.src(extensionPaths, { base: '.build', dot: true }); const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true }); const sources = es.merge(src, extensions, extensionsCommonDependencies) - .pipe(filter(['**', '!**/*.js.map'], { dot: true })); + .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); let version = packageJson.version; const quality = product.quality; @@ -333,7 +331,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true }) // filter out unnecessary files, no source maps in server build - .pipe(filter(['**', '!**/package-lock.json', '!**/*.js.map'])) + .pipe(filter(['**', '!**/package-lock.json', '!**/*.{js,css}.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) .pipe(jsFilter) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 2e5ef5277f8..bf962ab8c7d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -57,11 +57,11 @@ const vscodeResourceIncludes = [ 'out-build/nls.keys.json', // Workbench - 'out-build/vs/code/electron-sandbox/workbench/workbench.html', + 'out-build/vs/code/electron-browser/workbench/workbench.html', // Electron Preload - 'out-build/vs/base/parts/sandbox/electron-sandbox/preload.js', - 'out-build/vs/base/parts/sandbox/electron-sandbox/preload-aux.js', + 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', + 'out-build/vs/base/parts/sandbox/electron-browser/preload-aux.js', // Node Scripts 'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}', @@ -75,7 +75,6 @@ const vscodeResourceIncludes = [ // Terminal shell integration 'out-build/vs/workbench/contrib/terminal/common/scripts/*.fish', - 'out-build/vs/workbench/contrib/terminal/common/scripts/*.ps1', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.psm1', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.sh', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.zsh', @@ -138,7 +137,7 @@ const bundleVSCodeTask = task.define('bundle-vscode', task.series( ...bootstrapEntryPoints ], resources: vscodeResources, - skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-sandbox/workbench/workbench' + skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-browser/workbench/workbench' } } ) @@ -220,12 +219,12 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const out = sourceFolderName; const checksums = computeChecksums(out, [ - 'vs/base/parts/sandbox/electron-sandbox/preload.js', + 'vs/base/parts/sandbox/electron-browser/preload.js', 'vs/workbench/workbench.desktop.main.js', 'vs/workbench/workbench.desktop.main.css', 'vs/workbench/api/node/extensionHostProcess.js', - 'vs/code/electron-sandbox/workbench/workbench.html', - 'vs/code/electron-sandbox/workbench/workbench.js' + 'vs/code/electron-browser/workbench/workbench.html', + 'vs/code/electron-browser/workbench/workbench.js' ]); const src = gulp.src(out + '/**', { base: '.' }) @@ -244,7 +243,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const extensions = gulp.src(['.build/extensions/**', ...platformSpecificBuiltInExtensionsExclusions], { base: '.build', dot: true }); const sources = es.merge(src, extensions) - .pipe(filter(['**', '!**/*.js.map'], { dot: true })); + .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); let version = packageJson.version; const quality = product.quality; @@ -289,7 +288,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat().concat('!**/*.mk'); const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) - .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) + .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.{js,css}.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) .pipe(jsFilter) diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index e1072c1e665..08dfcfd0cd9 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -66,7 +66,7 @@ const vscodeWebResources = [ ...vscodeWebResourceIncludes, // Excludes - '!out-build/vs/**/{node,electron-sandbox,electron-main,electron-utility}/**', + '!out-build/vs/**/{node,electron-browser,electron-main,electron-utility}/**', '!out-build/vs/editor/standalone/**', '!out-build/vs/workbench/**/*-tb.png', '!out-build/vs/code/**/*-dev.html', @@ -152,7 +152,7 @@ function packageTask(sourceFolderName, destinationFolderName) { const loader = gulp.src('build/loader.min', { base: 'build', dot: true }).pipe(rename('out/vs/loader.js')); // TODO@esm remove line when we stop supporting web-amd-esm-bridge const sources = es.merge(src, extensions, loader) - .pipe(filter(['**', '!**/*.js.map'], { dot: true })) + .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })) // TODO@esm remove me once we stop supporting our web-esm-bridge .pipe(es.through(function (file) { if (file.relative === 'out/vs/workbench/workbench.web.main.internal.css') { diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 55fb148097c..f4202f8677a 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -59,7 +59,6 @@ const os_1 = __importDefault(require("os")); const vinyl_1 = __importDefault(require("vinyl")); const task = __importStar(require("./task")); const index_1 = require("./mangle/index"); -const postcss_1 = require("./postcss"); const ts = require("typescript"); const watch = require('./watch'); // --- gulp-tsb: compile and transpile -------------------------------- @@ -96,14 +95,11 @@ function createCompile(src, { build, emitError, transpileOnly, preserveEnglish } const tsFilter = util.filter(data => /\.ts$/.test(data.path)); const isUtf8Test = (f) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); const isRuntimeJs = (f) => f.path.endsWith('.js') && !f.path.includes('fixtures'); - const isCSS = (f) => f.path.endsWith('.css') && !f.path.includes('fixtures'); const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); - const postcssNesting = require('postcss-nesting'); const input = event_stream_1.default.through(); const output = input .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) - .pipe(util.$if(isCSS, (0, postcss_1.gulpPostcss)([postcssNesting()], err => reporter(String(err))))) .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation(token)) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 625fc430a94..88f5237d820 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -18,7 +18,6 @@ import File from 'vinyl'; import * as task from './task'; import { Mangler } from './mangle/index'; import { RawSourceMap } from 'source-map'; -import { gulpPostcss } from './postcss'; import ts = require('typescript'); const watch = require('./watch'); @@ -72,16 +71,12 @@ export function createCompile(src: string, { build, emitError, transpileOnly, pr const tsFilter = util.filter(data => /\.ts$/.test(data.path)); const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); const isRuntimeJs = (f: File) => f.path.endsWith('.js') && !f.path.includes('fixtures'); - const isCSS = (f: File) => f.path.endsWith('.css') && !f.path.includes('fixtures'); const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); - const postcssNesting = require('postcss-nesting'); - const input = es.through(); const output = input .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) - .pipe(util.$if(isCSS, gulpPostcss([postcssNesting()], err => reporter(String(err))))) .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation(token)) diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js deleted file mode 100644 index f916e9bf5b2..00000000000 --- a/build/lib/layersChecker.js +++ /dev/null @@ -1,343 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const typescript_1 = __importDefault(require("typescript")); -const fs_1 = require("fs"); -const path_1 = require("path"); -const minimatch_1 = require("minimatch"); -// -// ############################################################################################# -// -// A custom typescript checker for the specific task of detecting the use of certain types in a -// layer that does not allow such use. For example: -// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement) -// - using node.js globals in common/browser layer (e.g. process) -// -// Make changes to below RULES to lift certain files from these checks only if absolutely needed -// -// ############################################################################################# -// -// Types we assume are present in all implementations of JS VMs (node.js, browsers) -// Feel free to add more core types as you see needed if present in node.js and browsers -const CORE_TYPES = [ - 'setTimeout', - 'clearTimeout', - 'setInterval', - 'clearInterval', - 'console', - 'Console', - 'Error', - 'ErrorConstructor', - 'String', - 'TextDecoder', - 'TextEncoder', - 'self', - 'queueMicrotask', - 'Array', - 'Uint8Array', - 'Uint16Array', - 'Uint32Array', - 'Int8Array', - 'Int16Array', - 'Int32Array', - 'Float32Array', - 'Float64Array', - 'Uint8ClampedArray', - 'BigUint64Array', - 'BigInt64Array', - 'btoa', - 'atob', - 'AbortController', - 'AbortSignal', - 'MessageChannel', - 'MessagePort', - 'URL', - 'URLSearchParams', - 'ReadonlyArray', - 'Event', - 'EventTarget', - 'BroadcastChannel', - 'performance', - 'Blob', - 'crypto', - 'File', - 'fetch', - 'RequestInit', - 'Headers', - 'Request', - 'Response', - 'Body', - 'any', - 'timeout', - 'Performance', - 'PerformanceMark', - 'PerformanceObserver', - 'ImportMeta', - 'structuredClone', - 'stackTraceLimit', - 'captureStackTrace', - // webcrypto has been available since Node.js 19, but still live in dom.d.ts - 'Crypto', - 'SubtleCrypto', - 'JsonWebKey', - 'MessageEvent', - // node web types - 'ReadableStream', - 'ReadableStreamReadResult', - 'ReadableStreamGenericReader', - 'ReadableStreamDefaultReader', - 'value', - 'done', - 'DOMException', - 'WebSocket', -]; -// Types that are defined in a common layer but are known to be only -// available in native environments should not be allowed in browser -const NATIVE_TYPES = [ - 'NativeParsedArgs', - 'INativeEnvironmentService', - 'AbstractNativeEnvironmentService', - 'INativeWindowConfiguration', - 'ICommonNativeHostService', - 'INativeHostService', - 'IMainProcessService', - 'INativeBrowserElementsService', -]; -const RULES = [ - // Tests: skip - { - target: '**/vs/**/test/**', - skip: true // -> skip all test files - }, - // Common: vs/base/common/async.ts - { - target: '**/vs/base/common/async.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to requestIdleCallback & cancelIdleCallback - 'requestIdleCallback', - 'cancelIdleCallback' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/base/common/performance.ts - { - target: '**/vs/base/common/performance.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to Performance - 'Performance', - 'PerformanceEntry', - 'PerformanceTiming' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/platform services that can access native types - { - target: `**/vs/platform/{${[ - 'environment/common/*.ts', - 'window/common/window.ts', - 'native/common/native.ts', - 'native/common/nativeHostService.ts', - 'browserElements/common/browserElements.ts', - 'browserElements/common/nativeBrowserElementsService.ts' - ].join(',')}}`, - allowedTypes: CORE_TYPES, - disallowedTypes: [ /* Ignore native types that are defined from here */], - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/workbench/api/common/extHostExtensionService.ts - { - target: '**/vs/workbench/api/common/extHostExtensionService.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to global - 'global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/base/parts/sandbox/electron-sandbox/preload{,-aux}.ts - { - target: '**/vs/base/parts/sandbox/electron-sandbox/preload{,-aux}.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to a very small subset of node.js - 'process', - 'NodeJS', - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - // Common - { - target: '**/vs/**/common/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Browser - { - target: '**/vs/**/browser/**', - allowedTypes: [ - ...CORE_TYPES, - 'localStorage' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - // Browser (editor contrib) - { - target: '**/src/vs/editor/contrib/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - // node.js - { - target: '**/vs/**/node/**', - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - }, - // Electron (sandbox) - { - target: '**/vs/**/electron-sandbox/**', - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - // Electron (main, utility) - { - target: '**/vs/**/{electron-main,electron-utility}/**', - allowedTypes: CORE_TYPES, - disallowedTypes: [ - 'ipcMain' // not allowed, use validatedIpcMain instead - ], - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - } -]; -const TS_CONFIG_PATH = (0, path_1.join)(__dirname, '../../', 'src', 'tsconfig.json'); -let hasErrors = false; -function checkFile(program, sourceFile, rule) { - checkNode(sourceFile); - function checkNode(node) { - if (node.kind !== typescript_1.default.SyntaxKind.Identifier) { - return typescript_1.default.forEachChild(node, checkNode); // recurse down - } - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - if (!symbol) { - return; - } - let text = symbol.getName(); - if (rule.allowedTypes?.some(allowed => allowed === text)) { - return; // override - } - let _parentSymbol = symbol; - while (_parentSymbol.parent) { - _parentSymbol = _parentSymbol.parent; - } - const parentSymbol = _parentSymbol; - text = parentSymbol.getName(); - if (rule.allowedTypes?.some(allowed => allowed === text)) { - return; // override - } - if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - hasErrors = true; - return; - } - const declarations = symbol.declarations; - if (Array.isArray(declarations)) { - DeclarationLoop: for (const declaration of declarations) { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const parentSourceFile = parent.getSourceFile(); - if (parentSourceFile) { - const definitionFileName = parentSourceFile.fileName; - if (rule.allowedDefinitions) { - for (const allowedDefinition of rule.allowedDefinitions) { - if (definitionFileName.indexOf(allowedDefinition) >= 0) { - continue DeclarationLoop; - } - } - } - if (rule.disallowedDefinitions) { - for (const disallowedDefinition of rule.disallowedDefinitions) { - if (definitionFileName.indexOf(disallowedDefinition) >= 0) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - hasErrors = true; - return; - } - } - } - } - } - } - } - } - } -} -function createProgram(tsconfigPath) { - const tsConfig = typescript_1.default.readConfigFile(tsconfigPath, typescript_1.default.sys.readFile); - const configHostParser = { fileExists: fs_1.existsSync, readDirectory: typescript_1.default.sys.readDirectory, readFile: file => (0, fs_1.readFileSync)(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; - const tsConfigParsed = typescript_1.default.parseJsonConfigFileContent(tsConfig.config, configHostParser, (0, path_1.resolve)((0, path_1.dirname)(tsconfigPath)), { noEmit: true }); - const compilerHost = typescript_1.default.createCompilerHost(tsConfigParsed.options, true); - return typescript_1.default.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); -} -// -// Create program and start checking -// -const program = createProgram(TS_CONFIG_PATH); -for (const sourceFile of program.getSourceFiles()) { - for (const rule of RULES) { - if ((0, minimatch_1.match)([sourceFile.fileName], rule.target).length > 0) { - if (!rule.skip) { - checkFile(program, sourceFile, rule); - } - break; - } - } -} -if (hasErrors) { - process.exit(1); -} -//# sourceMappingURL=layersChecker.js.map \ No newline at end of file diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts deleted file mode 100644 index df3a3ef0e63..00000000000 --- a/build/lib/layersChecker.ts +++ /dev/null @@ -1,393 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import ts from 'typescript'; -import { readFileSync, existsSync } from 'fs'; -import { resolve, dirname, join } from 'path'; -import { match } from 'minimatch'; - -// -// ############################################################################################# -// -// A custom typescript checker for the specific task of detecting the use of certain types in a -// layer that does not allow such use. For example: -// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement) -// - using node.js globals in common/browser layer (e.g. process) -// -// Make changes to below RULES to lift certain files from these checks only if absolutely needed -// -// ############################################################################################# -// - -// Types we assume are present in all implementations of JS VMs (node.js, browsers) -// Feel free to add more core types as you see needed if present in node.js and browsers -const CORE_TYPES = [ - 'setTimeout', - 'clearTimeout', - 'setInterval', - 'clearInterval', - 'console', - 'Console', - 'Error', - 'ErrorConstructor', - 'String', - 'TextDecoder', - 'TextEncoder', - 'self', - 'queueMicrotask', - 'Array', - 'Uint8Array', - 'Uint16Array', - 'Uint32Array', - 'Int8Array', - 'Int16Array', - 'Int32Array', - 'Float32Array', - 'Float64Array', - 'Uint8ClampedArray', - 'BigUint64Array', - 'BigInt64Array', - 'btoa', - 'atob', - 'AbortController', - 'AbortSignal', - 'MessageChannel', - 'MessagePort', - 'URL', - 'URLSearchParams', - 'ReadonlyArray', - 'Event', - 'EventTarget', - 'BroadcastChannel', - 'performance', - 'Blob', - 'crypto', - 'File', - 'fetch', - 'RequestInit', - 'Headers', - 'Request', - 'Response', - 'Body', - 'any', - 'timeout', - 'Performance', - 'PerformanceMark', - 'PerformanceObserver', - 'ImportMeta', - 'structuredClone', - 'stackTraceLimit', - 'captureStackTrace', - - // webcrypto has been available since Node.js 19, but still live in dom.d.ts - 'Crypto', - 'SubtleCrypto', - 'JsonWebKey', - 'MessageEvent', - - // node web types - 'ReadableStream', - 'ReadableStreamReadResult', - 'ReadableStreamGenericReader', - 'ReadableStreamDefaultReader', - 'value', - 'done', - 'DOMException', - 'WebSocket', -]; - -// Types that are defined in a common layer but are known to be only -// available in native environments should not be allowed in browser -const NATIVE_TYPES = [ - 'NativeParsedArgs', - 'INativeEnvironmentService', - 'AbstractNativeEnvironmentService', - 'INativeWindowConfiguration', - 'ICommonNativeHostService', - 'INativeHostService', - 'IMainProcessService', - 'INativeBrowserElementsService', -]; - -const RULES: IRule[] = [ - - // Tests: skip - { - target: '**/vs/**/test/**', - skip: true // -> skip all test files - }, - - // Common: vs/base/common/async.ts - { - target: '**/vs/base/common/async.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to requestIdleCallback & cancelIdleCallback - 'requestIdleCallback', - 'cancelIdleCallback' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/base/common/performance.ts - { - target: '**/vs/base/common/performance.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to Performance - 'Performance', - 'PerformanceEntry', - 'PerformanceTiming' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/platform services that can access native types - { - target: `**/vs/platform/{${[ - 'environment/common/*.ts', - 'window/common/window.ts', - 'native/common/native.ts', - 'native/common/nativeHostService.ts', - 'browserElements/common/browserElements.ts', - 'browserElements/common/nativeBrowserElementsService.ts' - ].join(',')}}`, - allowedTypes: CORE_TYPES, - disallowedTypes: [/* Ignore native types that are defined from here */], - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/workbench/api/common/extHostExtensionService.ts - { - target: '**/vs/workbench/api/common/extHostExtensionService.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to global - 'global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/base/parts/sandbox/electron-sandbox/preload{,-aux}.ts - { - target: '**/vs/base/parts/sandbox/electron-sandbox/preload{,-aux}.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to a very small subset of node.js - 'process', - 'NodeJS', - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // Common - { - target: '**/vs/**/common/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Browser - { - target: '**/vs/**/browser/**', - allowedTypes: [ - ...CORE_TYPES, - 'localStorage' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // Browser (editor contrib) - { - target: '**/src/vs/editor/contrib/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // node.js - { - target: '**/vs/**/node/**', - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - }, - - // Electron (sandbox) - { - target: '**/vs/**/electron-sandbox/**', - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // Electron (main, utility) - { - target: '**/vs/**/{electron-main,electron-utility}/**', - allowedTypes: CORE_TYPES, - disallowedTypes: [ - 'ipcMain' // not allowed, use validatedIpcMain instead - ], - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - } -]; - -const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json'); - -interface IRule { - target: string; - skip?: boolean; - allowedTypes?: string[]; - allowedDefinitions?: string[]; - disallowedDefinitions?: string[]; - disallowedTypes?: string[]; -} - -let hasErrors = false; - -function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) { - checkNode(sourceFile); - - function checkNode(node: ts.Node): void { - if (node.kind !== ts.SyntaxKind.Identifier) { - return ts.forEachChild(node, checkNode); // recurse down - } - - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - - if (!symbol) { - return; - } - - let text = symbol.getName(); - if (rule.allowedTypes?.some(allowed => allowed === text)) { - return; // override - } - - let _parentSymbol: any = symbol; - - while (_parentSymbol.parent) { - _parentSymbol = _parentSymbol.parent; - } - - const parentSymbol = _parentSymbol as ts.Symbol; - text = parentSymbol.getName(); - - if (rule.allowedTypes?.some(allowed => allowed === text)) { - return; // override - } - - if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - - hasErrors = true; - return; - } - - const declarations = symbol.declarations; - if (Array.isArray(declarations)) { - DeclarationLoop: for (const declaration of declarations) { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const parentSourceFile = parent.getSourceFile(); - if (parentSourceFile) { - const definitionFileName = parentSourceFile.fileName; - if (rule.allowedDefinitions) { - for (const allowedDefinition of rule.allowedDefinitions) { - if (definitionFileName.indexOf(allowedDefinition) >= 0) { - continue DeclarationLoop; - } - } - } - if (rule.disallowedDefinitions) { - for (const disallowedDefinition of rule.disallowedDefinitions) { - if (definitionFileName.indexOf(disallowedDefinition) >= 0) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - - hasErrors = true; - return; - } - } - } - } - } - } - } - } - } -} - -function createProgram(tsconfigPath: string): ts.Program { - const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); - - const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; - const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true }); - - const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); - - return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); -} - -// -// Create program and start checking -// -const program = createProgram(TS_CONFIG_PATH); - -for (const sourceFile of program.getSourceFiles()) { - for (const rule of RULES) { - if (match([sourceFile.fileName], rule.target).length > 0) { - if (!rule.skip) { - checkFile(program, sourceFile, rule); - } - - break; - } - } -} - -if (hasErrors) { - process.exit(1); -} diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index ce744642551..fa729052f7c 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -251,8 +251,6 @@ function isNameTakenInFile(node, name) { return false; } const skippedExportMangledFiles = [ - // Build - 'css.build', // Monaco 'editorCommon', 'editorOptions', diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index 4cbbd3cdadd..2edc27ff55a 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -280,8 +280,6 @@ function isNameTakenInFile(node: ts.Node, name: string): boolean { } const skippedExportMangledFiles = [ - // Build - 'css.build', // Monaco 'editorCommon', diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 105dc02a79d..2a87c239c94 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -50,7 +50,6 @@ const fs_1 = __importDefault(require("fs")); const pump_1 = __importDefault(require("pump")); const vinyl_1 = __importDefault(require("vinyl")); const bundle = __importStar(require("./bundle")); -const postcss_1 = require("./postcss"); const esbuild_1 = __importDefault(require("esbuild")); const gulp_sourcemaps_1 = __importDefault(require("gulp-sourcemaps")); const fancy_log_1 = __importDefault(require("fancy-log")); @@ -187,12 +186,10 @@ function bundleTask(opts) { function minifyTask(src, sourceMapBaseUrl) { const sourceMappingURL = sourceMapBaseUrl ? ((f) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; return cb => { - const cssnano = require('cssnano'); const svgmin = require('gulp-svgmin'); - const jsFilter = (0, gulp_filter_1.default)('**/*.js', { restore: true }); - const cssFilter = (0, gulp_filter_1.default)('**/*.css', { restore: true }); + const esbuildFilter = (0, gulp_filter_1.default)('**/*.{js,css}', { restore: true }); const svgFilter = (0, gulp_filter_1.default)('**/*.svg', { restore: true }); - (0, pump_1.default)(gulp_1.default.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, gulp_sourcemaps_1.default.init({ loadMaps: true }), event_stream_1.default.map((f, cb) => { + (0, pump_1.default)(gulp_1.default.src([src + '/**', '!' + src + '/**/*.map']), esbuildFilter, gulp_sourcemaps_1.default.init({ loadMaps: true }), event_stream_1.default.map((f, cb) => { esbuild_1.default.build({ entryPoints: [f.path], minify: true, @@ -201,11 +198,11 @@ function minifyTask(src, sourceMapBaseUrl) { packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages platform: 'neutral', // makes esm target: ['es2022'], - write: false + write: false, }).then(res => { - const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path)); - const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path)); - const contents = Buffer.from(jsFile.contents); + const jsOrCSSFile = res.outputFiles.find(f => /\.(js|css)$/.test(f.path)); + const sourceMapFile = res.outputFiles.find(f => /\.(js|css)\.map$/.test(f.path)); + const contents = Buffer.from(jsOrCSSFile.contents); const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); if (unicodeMatch) { cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); @@ -216,7 +213,7 @@ function minifyTask(src, sourceMapBaseUrl) { cb(undefined, f); } }, cb); - }), jsFilter.restore, cssFilter, (0, postcss_1.gulpPostcss)([cssnano({ preset: 'default' })]), cssFilter.restore, svgFilter, svgmin(), svgFilter.restore, gulp_sourcemaps_1.default.write('./', { + }), esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, gulp_sourcemaps_1.default.write('./', { sourceMappingURL, sourceRoot: undefined, includeContent: true, diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 88a2a25dae4..d89d0d627f9 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -11,7 +11,6 @@ import fs from 'fs'; import pump from 'pump'; import VinylFile from 'vinyl'; import * as bundle from './bundle'; -import { gulpPostcss } from './postcss'; import esbuild from 'esbuild'; import sourcemaps from 'gulp-sourcemaps'; import fancyLog from 'fancy-log'; @@ -213,16 +212,14 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; return cb => { - const cssnano = require('cssnano') as typeof import('cssnano'); const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); - const jsFilter = filter('**/*.js', { restore: true }); - const cssFilter = filter('**/*.css', { restore: true }); + const esbuildFilter = filter('**/*.{js,css}', { restore: true }); const svgFilter = filter('**/*.svg', { restore: true }); pump( gulp.src([src + '/**', '!' + src + '/**/*.map']), - jsFilter, + esbuildFilter, sourcemaps.init({ loadMaps: true }), es.map((f: any, cb) => { esbuild.build({ @@ -233,12 +230,12 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages platform: 'neutral', // makes esm target: ['es2022'], - write: false + write: false, }).then(res => { - const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!; - const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!; + const jsOrCSSFile = res.outputFiles.find(f => /\.(js|css)$/.test(f.path))!; + const sourceMapFile = res.outputFiles.find(f => /\.(js|css)\.map$/.test(f.path))!; - const contents = Buffer.from(jsFile.contents); + const contents = Buffer.from(jsOrCSSFile.contents); const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); if (unicodeMatch) { cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); @@ -250,10 +247,7 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => } }, cb); }), - jsFilter.restore, - cssFilter, - gulpPostcss([cssnano({ preset: 'default' })]), - cssFilter.restore, + esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, diff --git a/build/lib/postcss.js b/build/lib/postcss.js deleted file mode 100644 index 210a184e5f5..00000000000 --- a/build/lib/postcss.js +++ /dev/null @@ -1,39 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.gulpPostcss = gulpPostcss; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const postcss_1 = __importDefault(require("postcss")); -const event_stream_1 = __importDefault(require("event-stream")); -function gulpPostcss(plugins, handleError) { - const instance = (0, postcss_1.default)(plugins); - return event_stream_1.default.map((file, callback) => { - if (file.isNull()) { - return callback(null, file); - } - if (file.isStream()) { - return callback(new Error('Streaming not supported')); - } - instance - .process(file.contents.toString(), { from: file.path }) - .then((result) => { - file.contents = Buffer.from(result.css); - callback(null, file); - }) - .catch((error) => { - if (handleError) { - handleError(error); - callback(); - } - else { - callback(error); - } - }); - }); -} -//# sourceMappingURL=postcss.js.map \ No newline at end of file diff --git a/build/lib/postcss.ts b/build/lib/postcss.ts deleted file mode 100644 index 9ec2188d13a..00000000000 --- a/build/lib/postcss.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import postcss from 'postcss'; -import File from 'vinyl'; -import es from 'event-stream'; - -export function gulpPostcss(plugins: postcss.AcceptedPlugin[], handleError?: (err: Error) => void) { - const instance = postcss(plugins); - - return es.map((file: File, callback: (error?: any, file?: any) => void) => { - if (file.isNull()) { - return callback(null, file); - } - - if (file.isStream()) { - return callback(new Error('Streaming not supported')); - } - - instance - .process(file.contents.toString(), { from: file.path }) - .then((result) => { - file.contents = Buffer.from(result.css); - callback(null, file); - }) - .catch((error) => { - if (handleError) { - handleError(error); - callback(); - } else { - callback(error); - } - }); - }); -} diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index d032969d9d3..cb44a1a2199 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -59,6 +59,8 @@ "--vscode-chat-requestCodeBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", + "--vscode-chat-linesAddedForeground", + "--vscode-chat-linesRemovedForeground", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-disabled-background", @@ -318,6 +320,7 @@ "--vscode-editorPane-background", "--vscode-editorRuler-foreground", "--vscode-editorStickyScroll-background", + "--vscode-editorStickyScrollGutter-background", "--vscode-editorStickyScroll-border", "--vscode-editorStickyScroll-shadow", "--vscode-editorStickyScrollHover-background", @@ -560,6 +563,7 @@ "--vscode-peekViewEditor-matchHighlightBorder", "--vscode-peekViewEditorGutter-background", "--vscode-peekViewEditorStickyScroll-background", + "--vscode-peekViewEditorStickyScrollGutter-background", "--vscode-peekViewResult-background", "--vscode-peekViewResult-fileForeground", "--vscode-peekViewResult-lineForeground", @@ -957,4 +961,4 @@ "--animation-opacity", "--chat-setup-dialog-glow-angle" ] -} \ No newline at end of file +} diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index 0da1f5c09e6..71c7a78e9a1 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -63,7 +63,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { const _log = config.logFn; const host = new LanguageServiceHost(cmd, projectFile, _log); const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log); - let lastCycleCheckVersion; + const toBeCheckedForCycles = []; const service = typescript_1.default.createLanguageService(host, typescript_1.default.createDocumentRegistry()); const lastBuildVersion = Object.create(null); const lastDtsHash = Object.create(null); @@ -294,6 +294,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js')); if (jsValue) { outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date())); + toBeCheckedForCycles.push(normalize(jsValue.path)); } }).catch(e => { // can't just skip this or make a result up.. @@ -387,24 +388,21 @@ function createTypeScriptBuilder(config, projectFile, cmd) { workOnNext(); }).then(() => { // check for cyclic dependencies - const thisCycleCheckVersion = outHost.getProjectVersion(); - if (thisCycleCheckVersion === lastCycleCheckVersion) { - return; - } - const oneCycle = outHost.hasCyclicDependency(); - lastCycleCheckVersion = thisCycleCheckVersion; - delete oldErrors[projectFile]; - if (oneCycle) { - const cycleError = { - category: typescript_1.default.DiagnosticCategory.Error, - code: 1, - file: undefined, - start: undefined, - length: undefined, - messageText: `CYCLIC dependency between ${oneCycle}` - }; - onError(cycleError); - newErrors[projectFile] = [cycleError]; + const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles); + toBeCheckedForCycles.length = 0; + for (const [filename, error] of cycles) { + const cyclicDepErrors = []; + if (error) { + cyclicDepErrors.push({ + category: typescript_1.default.DiagnosticCategory.Error, + code: 1, + file: undefined, + start: undefined, + length: undefined, + messageText: `CYCLIC dependency: ${error}` + }); + } + newErrors[filename] = cyclicDepErrors; } }).then(() => { // store the build versions to not rebuilt the next time @@ -588,15 +586,17 @@ class LanguageServiceHost { node.incoming.forEach(entry => target.push(entry.data)); } } - hasCyclicDependency() { + getCyclicDependencies(filenames) { // Ensure dependencies are up to date while (this._dependenciesRecomputeList.length) { this._processFile(this._dependenciesRecomputeList.pop()); } - const cycle = this._dependencies.findCycle(); - return cycle - ? cycle.join(' -> ') - : undefined; + const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b))); + const result = new Map(); + for (const [key, value] of cycles) { + result.set(key, value?.join(' -> ')); + } + return result; } _processFile(filename) { if (filename.match(/.*\.d\.ts$/)) { diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 1a68131f86d..3ca3a0d6ead 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -44,7 +44,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str const host = new LanguageServiceHost(cmd, projectFile, _log); const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log); - let lastCycleCheckVersion: string; + const toBeCheckedForCycles: string[] = []; const service = ts.createLanguageService(host, ts.createDocumentRegistry()); const lastBuildVersion: { [path: string]: string } = Object.create(null); @@ -315,6 +315,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js')); if (jsValue) { outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date())); + toBeCheckedForCycles.push(normalize(jsValue.path)); } }).catch(e => { @@ -424,25 +425,22 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str }).then(() => { // check for cyclic dependencies - const thisCycleCheckVersion = outHost.getProjectVersion(); - if (thisCycleCheckVersion === lastCycleCheckVersion) { - return; - } - const oneCycle = outHost.hasCyclicDependency(); - lastCycleCheckVersion = thisCycleCheckVersion; - delete oldErrors[projectFile]; + const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles); + toBeCheckedForCycles.length = 0; - if (oneCycle) { - const cycleError: ts.Diagnostic = { - category: ts.DiagnosticCategory.Error, - code: 1, - file: undefined, - start: undefined, - length: undefined, - messageText: `CYCLIC dependency between ${oneCycle}` - }; - onError(cycleError); - newErrors[projectFile] = [cycleError]; + for (const [filename, error] of cycles) { + const cyclicDepErrors: ts.Diagnostic[] = []; + if (error) { + cyclicDepErrors.push({ + category: ts.DiagnosticCategory.Error, + code: 1, + file: undefined, + start: undefined, + length: undefined, + messageText: `CYCLIC dependency: ${error}` + }); + } + newErrors[filename] = cyclicDepErrors; } }).then(() => { @@ -666,15 +664,17 @@ class LanguageServiceHost implements ts.LanguageServiceHost { } } - hasCyclicDependency(): string | undefined { + getCyclicDependencies(filenames: string[]): Map { // Ensure dependencies are up to date while (this._dependenciesRecomputeList.length) { this._processFile(this._dependenciesRecomputeList.pop()!); } - const cycle = this._dependencies.findCycle(); - return cycle - ? cycle.join(' -> ') - : undefined; + const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b))); + const result = new Map(); + for (const [key, value] of cycles) { + result.set(key, value?.join(' -> ')); + } + return result; } _processFile(filename: string): void { diff --git a/build/lib/tsb/utils.js b/build/lib/tsb/utils.js index 29cccf0f833..2ea820c6e6b 100644 --- a/build/lib/tsb/utils.js +++ b/build/lib/tsb/utils.js @@ -55,44 +55,39 @@ var graph; lookup(data) { return this._nodes.get(data) ?? null; } - findCycle() { - let result; - let foundStartNodes = false; + findCycles(allData) { + const result = new Map(); const checked = new Set(); - for (const [_start, value] of this._nodes) { - if (Object.values(value.incoming).length > 0) { + for (const data of allData) { + const node = this.lookup(data); + if (!node) { continue; } - foundStartNodes = true; - const dfs = (node, visited) => { - if (checked.has(node)) { - return; - } - if (visited.has(node)) { - result = [...visited, node].map(n => n.data); - const idx = result.indexOf(node.data); - result = result.slice(idx); - return; - } - visited.add(node); - for (const child of Object.values(node.outgoing)) { - dfs(child, visited); - if (result) { - break; - } - } - visited.delete(node); - checked.add(node); - }; - dfs(value, new Set()); + const r = this._findCycle(node, checked, new Set()); + result.set(node.data, r); + } + return result; + } + _findCycle(node, checked, seen) { + if (checked.has(node.data)) { + return undefined; + } + let result; + for (const child of node.outgoing.values()) { + if (seen.has(child.data)) { + const seenArr = Array.from(seen); + const idx = seenArr.indexOf(child.data); + seenArr.push(child.data); + return idx > 0 ? seenArr.slice(idx) : seenArr; + } + seen.add(child.data); + result = this._findCycle(child, checked, seen); + seen.delete(child.data); if (result) { break; } } - if (!foundStartNodes) { - // everything is a cycle - return Array.from(this._nodes.keys()); - } + checked.add(node.data); return result; } } diff --git a/build/lib/tsb/utils.ts b/build/lib/tsb/utils.ts index 59d1cab36d3..16f93d6838f 100644 --- a/build/lib/tsb/utils.ts +++ b/build/lib/tsb/utils.ts @@ -63,53 +63,42 @@ export namespace graph { return this._nodes.get(data) ?? null; } - findCycle(): T[] | undefined { - - let result: T[] | undefined; - let foundStartNodes = false; - const checked = new Set>(); - - for (const [_start, value] of this._nodes) { - - if (Object.values(value.incoming).length > 0) { + findCycles(allData: T[]): Map { + const result = new Map(); + const checked = new Set(); + for (const data of allData) { + const node = this.lookup(data); + if (!node) { continue; } + const r = this._findCycle(node, checked, new Set()); + result.set(node.data, r); + } + return result; + } - foundStartNodes = true; + private _findCycle(node: Node, checked: Set, seen: Set): T[] | undefined { - const dfs = (node: Node, visited: Set>) => { + if (checked.has(node.data)) { + return undefined; + } - if (checked.has(node)) { - return; - } - - if (visited.has(node)) { - result = [...visited, node].map(n => n.data); - const idx = result.indexOf(node.data); - result = result.slice(idx); - return; - } - visited.add(node); - for (const child of Object.values(node.outgoing)) { - dfs(child, visited); - if (result) { - break; - } - } - visited.delete(node); - checked.add(node); - }; - dfs(value, new Set()); + let result: T[] | undefined; + for (const child of node.outgoing.values()) { + if (seen.has(child.data)) { + const seenArr = Array.from(seen); + const idx = seenArr.indexOf(child.data); + seenArr.push(child.data); + return idx > 0 ? seenArr.slice(idx) : seenArr; + } + seen.add(child.data); + result = this._findCycle(child, checked, seen); + seen.delete(child.data); if (result) { break; } } - - if (!foundStartNodes) { - // everything is a cycle - return Array.from(this._nodes.keys()); - } - + checked.add(node.data); return result; } } diff --git a/build/npm/gyp/package-lock.json b/build/npm/gyp/package-lock.json index a20d85c70dc..08b6ae29b01 100644 --- a/build/npm/gyp/package-lock.json +++ b/build/npm/gyp/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "node-gyp": "^10.1.0" + "node-gyp": "^11.2.0" } }, "node_modules/@isaacs/cliui": { @@ -30,10 +30,23 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, "license": "ISC", "dependencies": { @@ -44,20 +57,20 @@ "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@pkgjs/parseargs": { @@ -72,46 +85,29 @@ } }, "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { @@ -152,13 +148,13 @@ } }, "node_modules/cacache": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", - "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -166,33 +162,23 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "node": ">=18" } }, "node_modules/color-convert": { @@ -216,9 +202,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -254,13 +240,13 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -320,14 +306,29 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -351,9 +352,9 @@ } }, "node_modules/glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -367,9 +368,6 @@ "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -382,9 +380,9 @@ "license": "ISC" }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -403,13 +401,13 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -440,16 +438,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -474,13 +462,6 @@ "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -492,17 +473,14 @@ } }, "node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -518,47 +496,33 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } + "license": "ISC" }, "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/minimatch": { @@ -601,18 +565,18 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -644,6 +608,13 @@ "node": ">=8" } }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -670,6 +641,13 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", @@ -696,57 +674,53 @@ "node": ">=8" } }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": ">= 18" } }, "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, "license": "MIT", "bin": { - "mkdirp": "bin/cmd.js" + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { @@ -754,66 +728,63 @@ } }, "node_modules/node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.2.0.tgz", + "integrity": "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, @@ -844,14 +815,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/promise-retry": { @@ -887,14 +871,11 @@ "optional": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -902,19 +883,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -963,9 +931,9 @@ } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", "dev": true, "license": "MIT", "dependencies": { @@ -978,13 +946,13 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -1000,16 +968,16 @@ "license": "BSD-3-Clause" }, "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/string-width": { @@ -1117,89 +1085,70 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, "license": "ISC", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "node": ">=12.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1209,7 +1158,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/wrap-ansi": { @@ -1311,11 +1260,14 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } } } } diff --git a/build/npm/gyp/package.json b/build/npm/gyp/package.json index a1564133a1e..2fee83064b6 100644 --- a/build/npm/gyp/package.json +++ b/build/npm/gyp/package.json @@ -4,7 +4,7 @@ "private": true, "license": "MIT", "devDependencies": { - "node-gyp": "^10.1.0" + "node-gyp": "^11.2.0" }, "scripts": {} } diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index be0c6c24282..1033e4ecf68 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -77,6 +77,12 @@ function setNpmrcConfig(dir, env) { } } + // Use our bundled node-gyp version + env['npm_config_node_gyp'] = + process.platform === 'win32' + ? path.join(__dirname, 'gyp', 'node_modules', '.bin', 'node-gyp.cmd') + : path.join(__dirname, 'gyp', 'node_modules', '.bin', 'node-gyp'); + // Force node-gyp to use process.config on macOS // which defines clang variable as expected. Otherwise we // run into compilation errors due to incorrect compiler diff --git a/build/package-lock.json b/build/package-lock.json index 212d9baef7a..626ec828a5b 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -42,10 +42,9 @@ "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/ripgrep": "^1.15.10", + "@vscode/ripgrep": "^1.15.13", "@vscode/vsce": "2.20.1", "byline": "^5.0.0", - "cssnano": "^7.0.7", "debug": "^4.3.2", "electron-osx-sign": "^0.4.16", "esbuild": "0.25.0", @@ -55,8 +54,6 @@ "jsonc-parser": "^2.3.0", "jws": "^4.0.0", "mime": "^1.4.1", - "postcss": "^8.5.3", - "postcss-nesting": "^13.0.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", "through2": "^4.0.2", @@ -444,52 +441,6 @@ "node": ">=18.0.0" } }, - "node_modules/@csstools/selector-resolve-nested": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", - "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, "node_modules/@electron/asar": { "version": "3.2.10", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz", @@ -1008,16 +959,6 @@ "node": ">=10" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/ansi-colors": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@types/ansi-colors/-/ansi-colors-3.2.0.tgz", @@ -1378,9 +1319,9 @@ "dev": true }, "node_modules/@vscode/ripgrep": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.10.tgz", - "integrity": "sha512-83Q6qFrELpFgf88bPOcwSWDegfY2r/cb6bIfdLTSZvN73Dg1wviSfO+1v6lTFMd0mAvUYYcTUu+Mn5xMroZMxA==", + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.13.tgz", + "integrity": "sha512-c62On5sLEIMv9yfetIZ8c0Mg0s6lvq54G0YLW2zY/XiPsJPyBBgrP54MR1s0hK3gsPy423cY+e2BDU6mOB2Yaw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1690,39 +1631,6 @@ "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1840,40 +1748,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2037,23 +1911,6 @@ "color-support": "bin.js" } }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/compare-version": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", @@ -2090,19 +1947,6 @@ "node": ">= 8" } }, - "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -2119,20 +1963,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -2145,134 +1975,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.7.tgz", - "integrity": "sha512-evKu7yiDIF7oS+EIpwFlMF730ijRyLFaM2o5cTxRGJR9OKHKkc+qP443ZEVR9kZG0syaAJJCPJyfv5pbrxlSng==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^7.0.7", - "lilconfig": "^3.1.3" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/cssnano-preset-default": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.7.tgz", - "integrity": "sha512-jW6CG/7PNB6MufOrlovs1TvBTEVmhY45yz+bd0h6nw3h6d+1e+/TX+0fflZ+LzvZombbT5f+KC063w9VoHeHow==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^5.0.1", - "postcss-calc": "^10.1.1", - "postcss-colormin": "^7.0.3", - "postcss-convert-values": "^7.0.5", - "postcss-discard-comments": "^7.0.4", - "postcss-discard-duplicates": "^7.0.2", - "postcss-discard-empty": "^7.0.1", - "postcss-discard-overridden": "^7.0.1", - "postcss-merge-longhand": "^7.0.5", - "postcss-merge-rules": "^7.0.5", - "postcss-minify-font-values": "^7.0.1", - "postcss-minify-gradients": "^7.0.1", - "postcss-minify-params": "^7.0.3", - "postcss-minify-selectors": "^7.0.5", - "postcss-normalize-charset": "^7.0.1", - "postcss-normalize-display-values": "^7.0.1", - "postcss-normalize-positions": "^7.0.1", - "postcss-normalize-repeat-style": "^7.0.1", - "postcss-normalize-string": "^7.0.1", - "postcss-normalize-timing-functions": "^7.0.1", - "postcss-normalize-unicode": "^7.0.3", - "postcss-normalize-url": "^7.0.1", - "postcss-normalize-whitespace": "^7.0.1", - "postcss-ordered-values": "^7.0.2", - "postcss-reduce-initial": "^7.0.3", - "postcss-reduce-transforms": "^7.0.1", - "postcss-svgo": "^7.0.2", - "postcss-unique-selectors": "^7.0.4" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/cssnano-utils": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", - "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2515,13 +2217,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/electron-to-chromium": { - "version": "1.5.158", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.158.tgz", - "integrity": "sha512-9vcp2xHhkvraY6AHw2WMi+GDSLPX42qe2xjYaVoZqFRJiOcilVQFq9mZmpuHEQpzlgGDelKlV7ZiGcmMsc8WxQ==", - "dev": true, - "license": "ISC" - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2621,16 +2316,6 @@ "@esbuild/win32-x64": "0.25.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -3515,19 +3200,6 @@ "node": ">=6" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", @@ -3543,26 +3215,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -3635,13 +3293,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -3712,25 +3363,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -3792,13 +3424,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3989,13 +3614,6 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA= sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -4046,522 +3664,6 @@ "node": ">= 0.10" } }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-calc": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", - "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12 || ^20.9 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.38" - } - }, - "node_modules/postcss-colormin": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.3.tgz", - "integrity": "sha512-xZxQcSyIVZbSsl1vjoqZAcMYYdnJsIyG8OvqShuuqf12S88qQboxxEy0ohNCOLwVPXTU+hFHvJPACRL2B5ohTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-convert-values": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.5.tgz", - "integrity": "sha512-0VFhH8nElpIs3uXKnVtotDJJNX0OGYSZmdt4XfSfvOMrFw1jKfpwpZxfC4iN73CTM/MWakDEmsHQXkISYj4BXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-comments": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", - "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", - "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-empty": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", - "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", - "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", - "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^7.0.5" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-merge-rules": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.5.tgz", - "integrity": "sha512-ZonhuSwEaWA3+xYbOdJoEReKIBs5eDiBVLAGpYZpNFPzXZcEE5VKR7/qBEQvTZpiwjqhhqEQ+ax5O3VShBj9Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^5.0.1", - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", - "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", - "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-params": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.3.tgz", - "integrity": "sha512-vUKV2+f5mtjewYieanLX0xemxIp1t0W0H/D11u+kQV/MWdygOO7xPMkbK+r9P6Lhms8MgzKARF/g5OPXhb8tgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "cssnano-utils": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", - "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-nesting": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", - "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-resolve-nested": "^3.0.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", - "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", - "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", - "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", - "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-string": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", - "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", - "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.3.tgz", - "integrity": "sha512-EcoA29LvG3F+EpOh03iqu+tJY3uYYKzArqKJHxDhUYLa2u58aqGq16K6/AOsXD9yqLN8O6y9mmePKN5cx6krOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-url": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", - "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", - "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-ordered-values": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", - "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssnano-utils": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.3.tgz", - "integrity": "sha512-RFvkZaqiWtGMlVjlUHpaxGqEL27lgt+Q2Ixjf83CRAzqdo+TsDyGPtJUbPx2MuYIJ+sCQc2TrOvRnhcXQfgIVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", - "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.2.tgz", - "integrity": "sha512-5Dzy66JlnRM6pkdOTF8+cGsB1fnERTE8Nc+Eed++fOWo1hdsBptCsbG8UuJkgtZt75bRtMJIrPeZmtfANixdFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.3.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", - "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -4924,16 +4026,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", @@ -5026,23 +4118,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stylehacks": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.5.tgz", - "integrity": "sha512-5kNb7V37BNf0Q3w+1pxfa+oiNPS++/b4Jil9e/kPDgrk1zjEd6uR7SZeJiYaLYH6RRSC1XX2/37OTeU/4FvuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -5067,32 +4142,6 @@ "node": ">=4" } }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -5360,37 +4409,6 @@ "node": ">= 4.0.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", diff --git a/build/package.json b/build/package.json index 64c0d7530f5..e34a23547ec 100644 --- a/build/package.json +++ b/build/package.json @@ -36,10 +36,9 @@ "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/ripgrep": "^1.15.10", + "@vscode/ripgrep": "^1.15.13", "@vscode/vsce": "2.20.1", "byline": "^5.0.0", - "cssnano": "^7.0.7", "debug": "^4.3.2", "electron-osx-sign": "^0.4.16", "esbuild": "0.25.0", @@ -49,8 +48,6 @@ "jsonc-parser": "^2.3.0", "jws": "^4.0.0", "mime": "^1.4.1", - "postcss": "^8.5.3", - "postcss-nesting": "^13.0.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", "through2": "^4.0.2", diff --git a/cli/src/desktop/version_manager.rs b/cli/src/desktop/version_manager.rs index f9f307365c8..e9cd1a10450 100644 --- a/cli/src/desktop/version_manager.rs +++ b/cli/src/desktop/version_manager.rs @@ -34,7 +34,7 @@ pub enum RequestedVersion { } lazy_static! { - static ref COMMIT_RE: Regex = Regex::new(r"^[a-e0-f]{40}$").unwrap(); + static ref COMMIT_RE: Regex = Regex::new(r"(?i)^[0-9a-f]{40}$").unwrap(); } impl RequestedVersion { diff --git a/eslint.config.js b/eslint.config.js index 7067b7ea730..dde24771a66 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -99,7 +99,7 @@ export default tseslint.config( 'browser': [ 'common' ], - 'electron-sandbox': [ + 'electron-browser': [ 'common', 'browser' ], @@ -406,10 +406,10 @@ export default tseslint.config( ] } }, - // browser/electron-sandbox layer + // browser/electron-browser layer { files: [ - 'src/**/{browser,electron-sandbox}/**/*.ts' + 'src/**/{browser,electron-browser}/**/*.ts' ], languageOptions: { parser: tseslint.parser, @@ -770,7 +770,7 @@ export default tseslint.config( { // imports that are allowed in all files of layers: // - browser - // - electron-sandbox + // - electron-browser 'when': 'hasBrowser', 'allow': [] }, @@ -866,18 +866,18 @@ export default tseslint.config( // - src/vs/base/common // - src/vs/base/worker // - src/vs/base/browser - // - src/vs/base/electron-sandbox + // - src/vs/base/electron-browser // - src/vs/base/node // - src/vs/base/electron-main // - src/vs/base/test/common // - src/vs/base/test/worker // - src/vs/base/test/browser - // - src/vs/base/test/electron-sandbox + // - src/vs/base/test/electron-browser // - src/vs/base/test/node // - src/vs/base/test/electron-main // // When /~ is used in the restrictions, it will be replaced with the correct - // layers that can be used e.g. 'src/vs/base/electron-sandbox' will be able + // layers that can be used e.g. 'src/vs/base/electron-browser' will be able // to import '{common,browser,electron-sanbox}', etc. // // It is possible to use /~ in the restrictions property even without using it in @@ -1203,7 +1203,7 @@ export default tseslint.config( }, { 'target': 'src/vs/workbench/workbench.desktop.main.ts', - 'layer': 'electron-sandbox', + 'layer': 'electron-browser', 'restrictions': [ 'vs/base/*/~', 'vs/base/parts/*/~', diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 7f3da5090d5..e5ab4067248 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -16,7 +16,6 @@ import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutp import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions } from './api/git'; import * as byline from 'byline'; import { StringDecoder } from 'string_decoder'; -import { sequentialize } from './decorators'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; @@ -1242,7 +1241,6 @@ export class Repository { return this.git.spawn(args, options); } - @sequentialize async config(command: string, scope: string, key: string, value: any = null, options: SpawnOptions = {}): Promise { const args = ['config', `--${command}`]; diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 1efd80585eb..9830928fcd3 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -13,6 +13,7 @@ import { emojify, ensureEmojis } from './emoji'; import { Commit } from './git'; import { OperationKind, OperationResult } from './operation'; import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; +import { throttle } from './decorators'; function compareSourceControlHistoryItemRef(ref1: SourceControlHistoryItemRef, ref2: SourceControlHistoryItemRef): number { const getOrder = (ref: SourceControlHistoryItemRef): number => { @@ -87,6 +88,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec this.commitShortHashLength = config.get('commitShortHashLength', 7); } + @throttle private async onDidRunWriteOperation(result: OperationResult): Promise { if (!this.repository.HEAD) { this.logger.trace('[GitHistoryProvider][onDidRunWriteOperation] repository.HEAD is undefined'); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 670e667c9f9..111561b665b 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -13,7 +13,7 @@ import { ApiRepository } from './api/api1'; import { Branch, BranchQuery, Change, CommitOptions, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; import { AutoFetcher } from './autofetch'; import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection'; -import { debounce, memoize, throttle } from './decorators'; +import { debounce, memoize, sequentialize, throttle } from './decorators'; import { Repository as BaseRepository, BlameInformation, Commit, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule } from './git'; import { GitHistoryProvider } from './historyProvider'; import { Operation, OperationKind, OperationManager, OperationResult } from './operation'; @@ -1554,6 +1554,7 @@ export class Repository implements Disposable { }); } + @sequentialize async getBranchBase(ref: string): Promise { const branch = await this.getBranch(ref); diff --git a/extensions/github-authentication/media/auth.css b/extensions/github-authentication/media/auth.css index 45c42c75ad5..26093f8147a 100644 --- a/extensions/github-authentication/media/auth.css +++ b/extensions/github-authentication/media/auth.css @@ -20,7 +20,7 @@ body { } .branding { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAlqADAAQAAAABAAAAlgAAAADkcSUjAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAxaElEQVR4Ae19CbgdRbXu6j2cecoECTIkICCGzAg+7qeQ9544QFQgiXpVEJTEe59ALsbMwE5AMZCQELgKeSoqDlyiQogCSUAC6FNCQhIwQMALCbNMGc68p37/v7prnz47++yzzzl76OCu851d3dXV1VXVf69atWqtVZaUg397IPJwSCKT46zgx16wh+19XSYnO6PjE7H4MDuZ7BSxXhQJ/On5KTWbTSMm3bYlvHXmJNxj2SatFLFVioeWn5lDDwAgMvOU2NG/3DMo2h691rLtr4bqBtVX1NVJRUOVBIIiyYSI3RkXO9q5NZmU5S98tu7XpmQF2AwAzCoNwMrAMm/CT3HkrgqJTI/KwodOD4ZDa5MVdUPtfe+KxONKiSob6+SIE46XqsYGK5FIBoLVtQEJiSRaWp+xktaNuzpqfirTLcBOZNJtdnjrDCk6wMrA8hOgWJcZoFSrT4nJogfPESu4Dv8AVLTTEqsC9MkSDHB2NIYfkZGnjJOqpiZQrkTCAmWyKqtDgXBAEs0tLyZtWRl7953/u/uiUR0sVgH2xuKERCJJnhc6lIFV6B7OuXyAJrIpqDzVlQ99TUKVt0scAEomQaXskBAONlEFrgpvLdkZk6r6Wjn6IxMx2qVeY0IsSVrhqnCgMiiJA62v4a5Vkmz/4a7PDWtmVXSIfGMSAGYVFGCpGvGh5VCiHohEAiJXi77sKx+eLZU1N0hHCyuTAJCCDqCAKIUCYxv4AcY6YnL0qeOkZuhQSeoo6b5OG+AC4ZJQRThQFQbAWt7Bvf8ZSNg3P3deA8ZUF2AzADCrMAArA4u9XMoQsQMp6nHlH5dKdcMcad+fJGVCCKRAxfMU1XKB1dYpI8acKI0jj5FkDNSti3LpzSgjCQQmrFA4HKiuIMAO2EnrtkBb+8pdXxr2OjOd+bAd2vQ2cro8mXPjwH/LwBp4H/a/hLvuCsr06cpky6KHfiK1jRdJ235M80ClABMtmMMfKJQDNB4j1VCstg4ZcTKANWpkZmCxAJZiABYIhgO1VRwi21HGj63OxI27pje9xGz5BlgZWOzVUgSPjEqufOheqWmcIq37YwBCWKsDDGlQULnAUh6L4CLFAh/fCmCNOSE7sNxi3IilxiUQCAdrayR+oCWBcn4q0eSy56c2PMc80+6yg2t4MEAKVgYWO7HYwcz8Ig9XSTz5kNQ0nC7tB6KoBmZ+bnAZdaVUhIMHVDxWQtQCYI3thWKZ8rrHLICiCwCslmIKFGn/GoPm9S+cW7ddswJgZw4Ta9NkC/n6HsrA6nufDewOV/Ap8x8cggHvMUztTpLO5igkCV2g4hMIJI3dY56mqBdwgXOlWP0DlhbNkhVgtoQDELza7Z1iJ2J3J2P2DX8/r+Evmsm2A2dukkBfAYbZSDkUrQco+IQ0XeY9eKwEraekovYkzP4OBhUrlGLEXYB1q6QnzXPYLUtuJ5SLcei1k62tMYDKDtTWnRusrf5/J9zbcv9xa5snc9aooLJti3xYbsVy1lEOxekBI01f8NBECVnbJVx1hHS2YSrnGf4y1SQTwAyYTJzpvr6lAWA2AWYlW1vidrQjGait/VS4pvaPx9/bvOn43+4/GwCzDcAoC0P+rKNd1ot9q1s5d489YIa/BQ9+Ap/yAxKsgJSJ8gGXUc94I1BD4Og/j91zDodYGFTmvf88VsYnpiWSt7ICNXWcoQoo2uNJ217298/X/8bky7ZcVKZYppcKE0OaDg0FDn9XPfwFCYc3YEYGUEUpTQeFUNTgyZniDBUyfJdecu9hVJjAYS+YbGuJ8z9QXXNaqL5uzQlrm3d88O79X+Ujt860MIu17ExDZJliFealkMJYMn1NQNZATnXlQ9+ScPXNEm3H02zKrZQK9PpoxQ5+PBJ3pVwqbkBJpFhjPiSNx47sWY7V60Nyy4BaQDQhdqCyJgRWX5LNLbuA8xUfaKz7sTtEkkhhMuBoU5QpVm792rdclKbzkyWoFj64WCrrAKo2SNMxhukSjb4CvoZe/pGBebyfP88ZTNx14KQX6BdV4McQSna2JZItLXGrsurEUEPdra8daPnv43934CIy+Qoqth2hDKx8v4hpkKbrAi++3AUbf4AlmqukvTmuiytgWfr3OBdg/bs533cpwMDgJ6BFEQtUVB4dGlz/kxPuab5TH8S2A1z9bGi+6/o+KY/8FKkUw6KH/ktqm/4Ngk8y6UHltvVCH39IrQx1SsXmoI9l5Tc72xS2Y50AWGtnaEjdF46/p3mtPuJqqmOUQ356YMZtYai8xITrfzsGb4Dg8392W6IZ8FOygImXslwe8KOzF0Dti2D8vZbO0KC6z37wdwfm/d2yvu8dvbPfXr7acw8YcULkrw0Sa34EoBovHZCmq3Jez7dlvwKkGMCoqIHn+E8tQtse5n1UwZn37HXVq0nOeO14sj0Ws44tD4U59FjWLEaaPue+IyXWskMqa11QUfCZQgaK6OtxhqeaIkpInjLUyiRBjJKMhRrrqkOh5IXlodB0S3/ilDR9/cngyx+RcOVg6WjtXZqe67OUUqVnJroQsgu+nTzF/oXUlsIUTIk/WaZY/e18A6r5D34MXbkV0vTBEoVKZ1Zpen8flv2+FCHLnq0YV4N2Zwcwb48pU6z+dDfVXiKnRKGh8HksJt+tIp44penZlmj68yBzD6DjRQ8pmQbwWR4u2aSau0oQg2JRT9EaUqZYfep9vEaKFGhFs2DDN6Si8m594Yk4RQyF+0gVMRlgwyTzb9qRIZu51C02+Uzc7WLaSS55eIvJh1WHwnVGWt0O+VM1eFgMg4dIXBY8OF+qar8HlRd0JciHRRutEoXUy/Q836R5kjIemnwmzpjJTcwlD7O6+crAytaZ5ppK013B5/wNy6W6/goIPkmlMBD1V5puCu8h5gs66GV6E7zHPZRRsmSrgOS7ZI3K84O9Bg/zN/wcuulfVYMHZ+3Mw+Hk+bmmuHT8pPgrZki/aG4qfVymWNneAfmp6Y5TDpm34T6s+306v9L0bA/ntSzAMZdK45qht4qXKVaPPeTM/GISWVcjnZV/xPB3mho8WL1ofPZY4AAuGHmWAZMpKv3cpPsgLlOsTC/BLNHMXn+YRAOPglE/ERoK3a1oMt1XrDQdDsne4YEEl/kv1vOzPcetUxlY6Z3kLNFEZdGG42Gk/hh00w9Xg4fedNPTy8n7OdBjgKRl+5RcudUqy7G8ADDS9DkPnApQbYNjjsOlsz1/SzTeZ/V2bKiQiZk/HUvp572VWcTrfaNYVLddvTUkgyYl5Rk2c1NARtRbUkIHX3nrKyNNn/fgp6BCcL8E0DUxuHShNL3ULzBFqVCR1KwwlZi3LshnQbkDizMkS61i2dkmqDa2zMQpr7tuDc3FQyOmNH0TtD4hTZ97/1ckFLxDEniBCbpvUZMofzQjHdzp5/6oZaoWuQHLMLOrXmgQO34hvugzUMJgfMnvIH5I9rXfgRfTJpqv9P4vU63r7cAYPESmx2X+A7NgQLoCtn68q7BLNL3VK3U9DT1pp6lsPjzoBVj8mqFWSzOfW549B7ZwP4W67RB1fgm7ACh24T84DZTsKlm2/WKZOX499Z1l512OdYoPG5yqkho8qBAoIXMf+K5UNiyAch4axbdXwiWaVAU9BwZQqdHPJCAPD82/55aSHbp17BlY/JrXQLVmOoa/VTtnSqjqVuFI2LI3itkJmX6nCLrICVceIRVVD8iNTy2RK6yrtVGGypWshVke7Bg8kCqJzF2/Gk45LoGMynEf5HGPl6WEIl/yAIlPTjstcmWyP86tW+ZZoWvCo65sbvrblVLVcCuGCFtiUb6MCjQshH/oOquAtQJMbgKzp6TUD7lKVjy9Ua57bJAaaUZ2dnd0kb1KxblKXtAYPMxd/zss0VwibTB4sEGlaF7sl8AXZP61TjhxX1qqirYO2alTPx0c3JHetbGbdq6SmqZLpXUfAEUnAo7NWA8NYLNjkFBXQJj4NpxkTpMrJj6ixgXPTLNTXut6uLkoyTR4WD0zpg5kB7+3EbrpZ6g0veQyqh5abyTuRBSP1dsM8iYwnMBTn91ph4afdKw0jDrWtqForq+oh6KKmWyDoegOLO/MbuXOO6V20BekdS9ngRwyu+ftsaZ2DPIfmI8je7Rtvlwx9vuatdRDo/FJNe/3gzDZg8FD3RjH4KE/SzTsinTy0WOH9OFCWrl8hD4GPwZYSSj/wmzBqm2y7Ff23H7cZ856Lzxk6LfjLc30nFw69R1PKwmsrqGQL57iAg6DK3c+CCadoOIyhnoh8dzXy6EVhvvohMQ7bQyN18mNf1sHUURdSYdGCj6pnDd349H4RmDwUD0AULH5hQBVT+WaZwF0YEYkGA5KKGRZrfsWyS++fHGgtvofYEpArArjpLaXl93jZYd5N9SE4oTkM5vAd0yQln1k0vvLI/HLsZXa1TbCX7n1nNyw43z5zujHBZ7iVLhaYHfQqRYbafrcB8ZhDHkEk5BG6YD7oP63LVV04Q8AKhIxGuZjsAOVBZtx4C1JJKYmb/78Y/r8aKLKrsYR1A0NBAtfryxPcIkuPPbajjeUVTuOhBcUfM11E6Aa4iy4sqb9/4fPJVCvNrhADFd+QCqr/irLd8zSCYFjht3zjDRLvft0SakwdniYd/+ZuG+LhMKNKk03fj77VFiJMiuDjnGwuoG860a4ED1RAKojbttSwxrB7zYGHuc1MS55AF4IGUjLIUO46RksuNp/lYpqmC/Bw1x+mVln1mhB07Ju6AoMjf8iL738FTy3UzhrjIzm8/IfyKjTfdCc+6bC1eYa/Z7jMcfggS33BsPamDjTtfQ0c55eFtO95XiPzT0m5jUGU0Z6XhvyQ3g6ViXVtr2LZeWUiOZH2044YVL0dT3x5w8oFhys2rIeqiEwX2rPN6hMqx0n+G37YuDdpsqoY56V67eNV1DphCHSxeuZO/obU/7GMjn7m/PANyFNX6MC3XiCzvgpJjk4mDQTe3P0lMb0TNd4rzfde+wt1+TzXk8d66gWxUYC8P4f2yvtLWfJjQAV9e7JA7NtPg8haTzsPzD7GyUt72GbMqksYH35PWJoxDBbUTUK85ptsvypb8q3x96mz/TOSPtbCTV4wGvlJGTu+kWwSr7GFwYPfWoP51RoA4e+1n2PSqdMlZs/97aKSCKngOJGUvDrXiyTDQnsfqX4Z7bOCqc6DsHwNRcnYGjsgJQbHVE3+FZZ/vQdcC7u7CGDnar6XQUtI+L4aJpz/01gdK8BTwL5Gx9Egwd2vJ//teUY+kIBsCRBgGqpLPvMGXLzZxxQcVbrNCCti5wFBCfRL+2juMGW0QoseAzRd8D30O2fzUlP6+nc5DWxN583jUMSCm2Dw/y6QV+R007aKUu3nqRrkpxMcDjrS6BQ10jT59z/K6luvAyg4syPQ6wLqr4UWIq8Nih5NYe+Vuk48FmAap72Az8YB1Q9VwrdfNA3w9wm3RybOD2d5yakX2N6+nWTZtLNPeYc17lXC10yp93NcxM8uU1Sj7HJa2JvRm+aHjueelsh1qiqOREzx7/Jsh0X6mSC7gY5NOYSyKRz2xC24zv3rccQ8iXsRYOv2/ECnEsRpc2jQ19ch77Ols0ST5woy6as06GPn5f5YLJV0tu1Jp83zRybuKc83uvm2Bt7jzOVwTTmwT9dGu6ERS+TvDSV58UKFViH5EaNAfB6PwXftVofTD6J4oJsQaXpYGRnr6+V79y/GUs0ZykPR9/lbgN9G7NdNjeoDGLoqw2h3jfJDWefJiumvJbaszBX0VTf6Hu2Hs3bNQwT1m+kgiIRiB1KFSxYZFNLghoGdYMvkRuf3iHXbzlOxQUEV6ahcZorTZ/1wAjoiO3AyzkFwx+c8XORHKjiv5+DLRz6sA8h9nZuOzBdbvjMLK1uLkOfn9vl1g3A2r8CM8I9mEFV4qU4MiXvOzHH3th7zIK85+nH5rynfCYdW2ugnJBK/Ctrx0qw6hm5fvsXFFwcGimxN4HS9DUQfH573YfwPTwFg4fjpJM7PKjmhcnlV3Bx1ucOfe07xI6dJDees0Y4pNPFRy5DX1cLfXsUkCtOxxZjyU/Br9M+fEFUiekClwFFeszmpKeZc+8102xeM9dNbPJ5z538FbrkEghWQOZ1pyx7apUmczcqUi/VTQeoZq37H3g9T8J90FDpgMGDoVR8kLdMf1EuZ8ivqgthdWO1LPv0eFn22ZecoY+yKX+syjivYWC/kLxjFjYba3k37RwHbYQ/gU85qgDS977WMgyhZhL1SGJovFSWPf1ReIs7T2aOe1ULunTt2Vgp+L0EQcSiEF1YWDqiSoluDwKGg++HE0vDexhwmfO+1iYv+S0ubVHUksTQ9zVZfvYdWqzK71SU0L+nOFIv517vB9W/0gZ+l9vHkOSCtyIluHz0yxK1x0GV5GmdoeCVDfwpAyqBogJ82Zg1Vtd/RIKBF8B7nSUX/OJf4drz90qVYjHOBimecKgUY90ShOfuMaJU8B6nEgt+gFePpZlqLCBH25/DBzNaQUVAkXfMhwEK21Wath3ceW5d+PIkxSTPH7tXmk+eiOn6I1Dwo2ZDqcHF2nHxNY79jaskWLleRh33S2irQqVQfVIFHSDh3XHWrpQJLdPGeWMee/9ZbFECJkSY7VbVYcWh+eey7AmAaspz+iETUO4uDgOuSUkpcebad8mKuGCrZFlnh2fKsr/9FtP/86TNVfSDWkbmIoqQylljPEaib0lTExaGwra8/lpQosB9GE1QIOEqPxPW0tSUMa9pwEnqnIkmk3s5/1EUCo8V0E0TiBJmyvJzVusj2Mfs6/d5cCiWaSS/IkqxGWaffD70qWBoMIgyIcq4Uq9Irxfzx3myA5t43JKamoCMHClSXw+aSlkoMpihzwyFhKFJ1/vdPE5ZXdfy3Q5oTqFI6E5BRTvW8SK2lx+voNKPNgLWA338TxC6A4sNphRbjSkw/s8+eaa07fsurFhI2dhhfF2lDWTQoaggQVTpA0eJHHY4XiPOwetrDVNgwrnqiBtApRDl5DOtYP78BQ5vAlCFsSxzl7zcClHClB2iyoYAVARrmf8koWso9DZYFfHwdX0Y9oHTRy/CrOxt8AkrIZJALjpcpm51Xl+I9+m9H3MU05UQxEOHiVRXibwO7SRSrwoOjahbgJkQWE0e8oVztmji9Fkj0wcWMPRVVKiKTtuBy+XGsx0xiVKqyYXlVdlG/vshuN2YGVisoH5dnLWoOOImyJPeAc/wC3QcGGa4xnUMLErfFFaltk5k5CiRN94QaW4GuCBrVPGDCyRvLfkC2Hj+6zEO9Nh9MzzOOWhm3hhXteGOlldBTc/HssxmxzrpGbsYQ182omua21OT3FZrFzBPT+e85u0ak8+km3OVxOGkZ2DxDn7iEXQaxREzx/4SDD1M6m3sEAqO2ewQakrU/O6Pvije7p5785hrvGSu89jk8V7PlMdcN/eS0pih8UgMje++LfI2qoklOLXUZkt1wOcDkFfv1x8cI874Vkw+VgCB2RlMHXmsRejegwGIZ8JYjlon1rtfkhUXtOrQNx1C3CIFVstbtfTHZrtm8qbnST9nvkxp6enMw/+DeSzmTA86Y4Qa8eyT16NHT8UMrRVGCWTqM89uTA3MU7zlmWtMM9fT09Lzm3zmHhOn0vGWafJPkAw9TOQoAIygUb8eyJxi6HE9xXchnflT/ywUQctkunPaleY552HStZgJhi3M+uZBNgVVF4CKSzOR4oGKVTG457FfQm7AYm2pm04d9dknPwFqMB4znjexvkjtg6J9mVk7jUBiIPWqw2zxmFGC2SNqh+oRPBnB5QJIQcRjPXDKMYBzzry/zARVH7Q9EXsL1uEfx1rfUkdZ8dBQG/Y2plDHuQOLNTDgumL03/Gmxkln6y61fPYLuFhHMzSGgfkjj3aYezL1pGgMBjA8V+rlUjoDKr3uZE3l7zoFakHLqDbc0bIRVk0nyopzHtO1Pi4eF8ukras+vj3qG7DYDIKLPNd3xr0lweYJ4C0ehx2iX6T0TkcTXAZIFEccBYBxFqgyL2QheMyQmKJkSNc0FpGJenHoCwWx3hfAWl8EVOosWXnuPh36etPwZJH/ZKEX5r2H3lCeC7PFKyzunv1Ruf6pP0BK/xnIvEAaqFvFt1jqgCpw0OLQWN8gMhLKjG+8JtIK/1ecNbKGJGLpNeU9BCbTnWOagsZgMQOFRGiAWPHpECVsVIuZnR+Gh8PpmflM3F6UwDawnvz3S0Bd+k6xTOW5eG10pOaMPRuLxXCuTyl90hkuTL6Sx0AIlxXDIKpHjRQZMsShXPTa56VcqSHQTdcXBbJnQ2BWSWPR1kelDUPfcoBKVXci9vtFd6oQr6j/wGJtqCNFjUeGOWMuxBLQcixekwriO/KRLwHv0Hj4CPBeRzqgov0qAaT8Fqps+C4m2kkYY0BmEa4KghIvlZXnnCG3nveWZ+hT6LHp5XBwD/RvKPSWo0wrZkMMc6zZGBbfgT+t66DRyY6nkagDPM1Qwh+Ci4FDYwMWsisprcfQ2IGhkYw+KRazONloLIq1vvZWiFa+BJP2dchgybQ1MBYt8dCnjfD/z8AolmkfZ0NXA0JcvpgDt0Wdzd/Al45XFODSD4dG/wQCjOCiAcnRI0WaBrtMPb8D12KmAgvIHa2bpS3+IQUVhz6G94nasLalwD8Dp1imgo5ukSulH/NjuQGUywrdA78JQch7yGLmB8TmeQONCS6CbPgRWDSGu5Z/vAF+CkNfTS1mfXtXya1TL9dHqCXQIaLmwm/DBFJe7znT09PMuYl7upfXGdLLc1K7/7p58/+yOWNUccTYtVhS+QgsaPaBcPE5BJd/gndoHDTYlpHHBeB36oC89drZCirObA8VixkltmldmwkE6Wnm3MSmiEzn6Wkmb3rMfPjPP7D4oK3u0/78Z5FX9jhMsVq551o79/5iRDo0gomvAs818liR8ad1PXVa12H5qA89AKqVf2BxrYwCw5lrPieHD31C2jub5OUXk2CCAw7L5UNwcUxMQKHLCjTAKvsPct2Om5Bkq24aqW859LkH8ggsDB1k3uli59/v/jqMYO9Rvj2AmWFHR0BefglMMhza0LKGMzD/hQDUgRyj2fpBl8l12zdL5K9HpuwBVHTvv0r7tUb5AZb6bVoMYEFL8lv3zINu0o/AsBM9CTCMWAbBHCEOATXB1Y7pvU4WcZk5/PXfZTRLy6Cq6ufke09+VsHFehq1bb++TR/Va+CzQjK4EXe/5MvWLoerSeyXDPdBAVCwpOvFlxQqRHBhJrZnt7M4TOU8MzPzUYe4VaG4gS6FatGetfK97ddjaJyLa47RrB+NIQh8PwTOClGXgVEsgsrIdi5f+zOs+l8Bwahj7UuzJz5E//mDgDVcPX9lt8iB/Y7euj+HRdbWMZqNtiakfvAc8F2PSmTHYV1Do7aE+UobONf2C6jYE6gL33b/gUV+KgWqdX+AT6oLoOnARWhnhwcDKM669JhPRSCPxf/XXhHZS01PHPuqZ7SW5of9E3CNZj8mVbJLrtl+loIr4oojTM4Sxn7CFbuB9enfUKhakpNj8h93VUuyGvsl130UVilw323R94MTCCg9YYygp7hI1RQaOpB6vQEDCA6H1PrkOp03v97kgx/HnpL6V1CbqWgCv7hevrttsSy0Ilq7EgtQ2Wump33QW6kq9B1YpiMvvW8YhJ+PqdO0Ds9+yYoj/Ojam56kHqYHmgRwMaZFzVtvOuAaNtzBlR/B5bSAGqPYGAFbi9QPuVq+t+NfJJqcJpEJ+4QuLrlDWjmkeqBvQ6HZ4eGydcdLAO6DKqpPhI4SVZOp6OeARYHhAsekKZjwo8OiJ+Z16kbRAOLNV3nm5NElO5RhyvJLTHea3MiJ/iRq6v+3VFjPy7VPflxBxRmj2mNqK/7pf3IHltnh4fJ7P4IZ3zZoUg6Hdxp+pQ6oTFfqEOieKKBwzDh17AKLwyH/mR/meLJ/L7QN9jhDIvdB9CtTr0qMNneIiKEPhmH7lEfku9vnOoa+WIw3C9amP4oR+/AbzA1Y7Cxanlz++08CCI9DRbcWPgnIqGeWSnvBpR0L8JiYh/qPH+bTf6RREa8FNoFcAqKtoJ/B5bQFewZhO7141IaD3u9DJLFOZu+o1VUHP26np3Uu3k8vwDLSdCzRzFr7ZciiHgAQsPwBuyobfEW2LyVFokxjCCIee8BkQMV02v8RXNSPenk3zOYxwhpBKm/LFkw9TB6eDySY+03MsjIeu3I6qmTXYM+gwfYuWbLlNLULoHZtUYdGVtD8pzc+vfLefOnXvPem5zN5M6V3v9YzsChNp2Ibpemz7p0l4dpfqOeUJFSPnQ0wvTXo4ViR5FxLgYqnBlxpMTuGSnc02SK4OqBST3AZIwfTnvTYeUL3fk3P05dzlsf8DOa+no45a6QzXe4ZFKr6APjOv8o12y4v6p5BWlHvj6m0idMrb/Jma6S5Zu5Nvyc9vft5ZmDpEk3E0em+7N5rIX1eIdFWzGyVq6bgKfdAqmSCF1wpapUJXHgEh8NXXoILoBa/C1JN6+CyqBOUPI7t9AatxKzxLrn0vkp1bPdPODQeDCxdook4QqVZa2/D7GchZDjcL5mwQH7zFfQh5p0KKjc2YPOCy0vF+Koo5+KzXtkNfwz7HHBpAp/r2wALJdSNGyPUNE6DEuGzsuTJcY49JgTKzpYsvq18PivWHVjdpOlrfwvd9RmuM/787Jes4HIB6QUaW6Tgw4+Czb1IqTyZ+NdeFtn3btKR0pMZ85GhxsFvg5V39wyqHgWB6na5ZutMZSnoaIV9XKrQn2+yP/egfV2NNNJ0xjXDN0ql2S8Z0vR8BgLHK0pgxV0cOY/xnDAflU8tGIu+9aYzWRgCt0UJOF9znMH1bVjOZzt6L8vZM4h9zD2Drt3+LxIf97WUz9d8LWSbBYtcAZBrPm/7+noP8jsUS5XzoEc16+4mqR6+RSrqzkjtl8xCc/lnRUw+7zHTTEgde8Bz0LCIzLys/5oPFjPwkxCQDnn+2bPgYO1fwSTjOrh6G4Ya5pn+jDE0grpS5lXb9FUJP7VTIls+5Kw1gnL5wrDXvJz8xpj1oYFUzrt83dGSCMAZf81Y8FQHO+Pv7bkp0CCj95j3mZdujhl7yZSCCyDSYZCXcBzACwnAKLYGxqLxts2QPxwvd39zo8w/+ddQGDxHqR73Rwa3rMX59kc3RjBD44cw690JvusCHRqppVrKobGAfeaIEy5feyImfNg2pOooSNO7lmgK+GAt2lArnihxYkyAYeijAUYl9pjpaF4lt00/TX70lVfVWJSqwgvH/AE5T8eQ2AEzMw7n7jpdOqJxJWPIlC/XtPQCM92XMQ/ccbt7BtU1/QwiidWai+Kc96H6c0C+xh1W7fWQvzTBNVHxQGX6vhu4lGphjxnsLCrJKDZvmp4yw1KLGVBW9RsBd0oLxv4FIJyAOr+NZRXkx7ZsGviie/tnxvQ8uablcl9PecBv2VDj0D2DBl0i127bLpEnjtU26VIQBNLvkxCQ+v1XQJfqGHxNUEjHGthBHZ7eSQU4d7qTbChcLtLbMPaYSdgnyQ/Py7zHjHGntGDscxhaMHS3/re6asy0XYvBi/eFsQnpwdsscy0930DymHsdvha7fWEhu7J2HDZAfxYAm+bsSYih0fjDMHXoT2yeZervPc+WxmeZ6+bYe6+pS7Y0Nw+1PKfiq+dp1wzRFFC8OA5+Co72MfS1H1gtt5w7Xn54/otZ95gx7pTmjH5T4vvGw2nHFvVbZcDFupsO8B6bjjPXTOxtqzfNHDP2BpPONHOcLY+5tysvvdeg3dgzqKr+Llmy/SbNYvYMMvlzibvK7A4M3ptep2xpJq8pL/3ZmdLT09xzzgpH61INfSyYTMWMsYCDWV4Iin5JqDVfAFDN1PbopAJrlNmCDos2BI+TW2Th2FMByg3ujhrZ78tWZlGvweWTs2cQttNrugx81+MQS3xAh0blu3IbGv04ftIqJTOqC93BxtF+pTrafw6mYqNl1bl36CyJ03AytbkE406JM6xF4z8Jge6vAK6wTgDcvWNzKaaEefhxO0NjVd2pON4li7dOUXDx3ZC37CUwmwnmdZrYpDP2ppljE5t86efp96XnY34TzDFjSh//Bv/kvAYd4aIFGFzgj3vMdGCPmSHbR8stU59z/E4BUI4fiNwr43WntHDcl8G/rJJqgIu+rfwtpfe2EbNG1zKouv5eiWxbqv1Au4JeZo0GDObFegvNdC1bPnNvT/eZdJOPsTfNlM09oddgys7ruVEI5hxIsDj0VYKfAwFvb54Jby4XYg3NWeoYiMtFvgCqqZDaLRp/OcB1JZakKOci3YLDD1Ta7/9JWgbBRq6TlkGD5sjiJx+V2X/2n2VQDu8/IC17V2L42A1xA/z64KUXKujQR5eL2F4tFn0RBHI8+KnVOvSpNkWOQ1+2+tGdEgP5s0Xjr8WOW/8GgS9oozqOKCZFzlbL7NccXjfoWAY1fEzqq3fJVds+4YhZImARXF9k3lL89MGwXqhPQH56UQcsts6CuOE93ahR5UF5rimXXshhco+Z9pY1MrTzJLn5vB3gHypSi7PejhrIsUqzAVLKha4afytEF1NVpyug6hLFocoDqT/vdWR7mDU2O5ZBVdUb5KqtEYey4+Pxqj87n9JAn5i/+wkdBGdJZ9WUFyCQxA6rHS/hCyfDxSWd/AwdOuurgHYEFNw7sMfMLZ+frmrOpCrc17lQgcMqeZNF434LmdhkDDHc8zAEgB8iM0b2P3eOhWVQtD0J58FXy5VPbpBZ25oo83r++TfyqxyQ5/fgLOlwEfrm818Vq3U8KNc2LEI74BrYwwBNDH0sK9b5GmZ9p4FKrdJZTr6Gvt7qZ6T0V47ZBHP5SRCr7JMwpfQ+B5f71euXzaGRE522/di0oP4TUpvcJXM3f/z1mUdAhxvB5jDvv+BUiovQpCA3f+WADN1xCuRJD6oEnIx2/4KjdVCFPWY6WtZJdcuJcvO5m3XoUyZbFQn7V3Jf7zJS+oVjnsIEcTx2Z31FKqAtQUGqUmXi32f/rJjWCY1lHeFgCb/caTYGqnsY+OFHAnO3LGJXBMIWWBkeaU498MOPUyVTE4LLyI++dc+vAa4vYvji0EGpfPe85p70mGAM0CoCIR6dJ/953lI99patCUX+4bBICjbvsUFwB/kIpPRj8KL44fhzSCGgGBh3Axn41aQdsGqHWPZbr99+zLgT3gsNGvrtROuBBF5QrzIvLbPQP6jzwWDxOvr41t2rAK5LIWvijIp5s5FdKi9j6KvlUsVbYECnQpTwmA59o6fZUHArPZtJptfwXv8IboT68Bk6xPgRXClg4cALLvZiEjIVKDraiXBo2PB6qR9xmG1z59kMr7PQGMpYPup7MFCcocqRB91y7mUA1VVYLOVyDypuPCBrS1EmYwam47OiFL2zZSPoG4Y+BVWFOg7xA6hYTYKKlJOU66oJZ0rr/rtTS0CHgpReuxs/jmVQCPywI0JxztlC34SDKZapGgWN02H+RaB963ffxHreDzGzwteC3bNtBSTvZZOgkIehjzxkLBqRH5y7WIswWqmmPD/FNIfnFsUMS7bDYKRxBgSqFEVwKOm5T5i/WKELROxl/W7R06RW+g+KJXZnTIYdNVzqh9ONBprjVUEqVj0zPQd1660TIZB7GIaXkAv9+2+mADy3Y2gcgoVTp6E0dCCgOg5g1pe8WH4wbQNkLQHhHjMEpJ8DBY0RvjK8osg27HvduACCYjNcH0zJi90W1Ayd7DyVkbGtzAiswwAsfBeHELCchhnqM+OuRqkIXoD2noEL8LxvvY3GPCTh2B2yYnq7sx3ITH75bo84t/v210uVl2ybBf5wBfhDNAvrpqXeUUN70O1Gwt0w8OnAOhIUa8ShCiwio7dZXW/X/YsuUOVNDlVevA1uBCp+4Qz5HPd1NlyamucCrA53KDykgcXu5Rc+c3VI9g5KymhspE3m//UR2FptBumw+3mV5j0M+KlGHBHZ+ikM7/dj32sMP7EYKBcFqsUP2YCFncu681igWLQcTx8KWUZPzE62awNtLcru6bEDLfrQvJ+m8CpQ3X4q3tzD0PqogXYt5XjFB1cmYPXIvPtvKCw9k+onCBopfWT8ZkjksLzV+aYadqSk9KgsX3gx/k2/pD/LpPs8LgMr/QUZXfprJ2Bh3sK+123P6/JWIVWK0utgzg2ozPkhNL6UgWVemjemAJU8VwT7XjcMHQ8h8eNqqFEIlaKeyB9ngb0FD/ByyN1baXm9fgh9A3ltd26FRWysnVqcHWJW/OR9kNJ/GoLU4vBcBilGzGBil8/iAhkFpEMpIPXhrLBMsbJBzBhqKLAmcjN17HsNXXpHjdu8+mwl9P9apk9eKRR+0p9szvU6HlnK2G1xGVi9vXqvoUZk4oUA140w8KW2B2hGARfWDThYP++x1tckmNjNo9dK/ONWKdN3UeKa+fTxRtecC+pXbZkPcH0PqsPU5GdX5v8DZakMZgg0cZrkXYfC4Yf7bkkn/x3idMf775eAigBEXGFYcgo3U79EQtXQ7cSCKXX6CxIMunoo3KUOPVwtaXIZWH3qfi5Yu4YakQk/go3A51XafUi4U+pTQweWGSpIZWD1pwtVrwtS+iUT1ko8/nFVJaKtZKENNQyFMnF/6l7Ye2wrCIc62GukDKz+drSR0l8z6TG4s5yIPXbew9BIXXqKI7oYbgOCvsbeepl7vWneY/JfDMqH4bhEMRj2hAXjZxCsp8vAcl5J/34NuK4av1OsTpjPte+GQa5jqNG/ErvuUjDhVbmY6bqQ6cgFU6ZLRUyzEdRnbFIeKANroB1vloAiH31VAlEsAbVsd6T0/bZw8tQoJ1Qhvy8m99ysPZxs2d8mVujnZWB5XmO/D7kEREONyEcPyMkTTsEuFX+ERir2boRdZSGDUrVCPqAPZYMFCNbX84Yluy+qe7MMrD70XdasxlCDAtVrJv4vWP+sgadkDIsElzI9uN0gIdfYPNGlXOnFmMsljdFeLNAHmxorE/v33bv7oqalaK4/rWhL2k8DeThFEcaf1ZKJ0+HY4wegXOS5YAuI31zx5OJI8cib9L5UImpojgdS2QHfS0DFrFA4GKxrrEjs3/9fuy8e9DktdbFaQgz4AeUCvD2g5nOuO6VrJv0fDItL4E4JogjVsOUScm7BYMfEB93FC+b/oIuFTEiAcMYx+wsGahvDyVjnq4nmfReDUn1RH6pGKlayPBQW4hUYO0qamV0z8WpYMV0KhUH0tY4Q+ZfSG/AZnBUmTuDTiFsVNcFgbWPIjna8YLfun1H38uvHgVLdnvJN5rbdF9OJQrxbn5RJf1Yw1IDqzZVPfhEbiP5aN1BLUkG9F0MNBQt+UiBxj71rhdCaHno07QoLt1YIgGA7M9BbGC1zM5Bk+4GnwUIt3XNxw69cKixnPmyHNk121Yvcji9TrMIiEEtA6HAqDV4z8U5I6T+hzmyD3GW9Hx5vCDIGAzZzrIn5/eFwhxITVlVdKFBVF7Q7W59IdLSev/viprF7vt74S4JqEtsFlKWDijUpU6z8vo+eS0sZamyBlN7aBEONejXUsGCoYQDjvVvT8GNAZGaEXoqVUvTLH8VS/snCnmvVDQFa/tjR1sdsO3AdwHS/Wz0LgAptnTmJZkGZaq7ZysDyvsxCHxtwLdoxCiPMn2AgewQc2mb2eJMJWAQV0xHzlXZpkA4YWCiNQ5kdCtQ0WuCf4NmscwOmsd/f8/VBD2u3gDKduUmCmahTpm4rAytTrxQyLWW/uGUoKNejMNQ4CTr13cFF8GggenDgpVY89wLr6BHgsQ7DOjidTff5dbI0DnlhAEqSHdjNNh5fiw3Llu75+pC/IB0q2aqoAHcE3XkovZblp881yVJW+VKuPWDcKV36QqU0qJT+dPiN6AIXX7cGHPA4G7COGiF1AFaffDfQsw55KMsCoBok2bqfT7sTz7l+zzcGbdNHc+uVZzZh8jGZwOtzKAOrz12Wpxu8LgkWbr0XUvopEKimGWp4gEUJmAGZl2L1DVgsAYAKhAO19ZJs3o9j+Zkkg8v3XFL/rLaM9frwmbZulj6AppaBNYDOG/CtpApcAmJYuPUnMNS4SNr2gmO2HP/0TDdgMjEBRmDxkjLvOVEs3pWA92hQqDpJtuxrR7k/hl/AFS9f3PQirolAZCBvI9XURxP7/1MGVv/7Lj93upJqLWzh1qWQ0s/pcqdECT6umKGQxwAV05R5j9L8C8A6vMehUAFlBcNhq7oGgNp/wLKTt8bDVatevbDmNT6TIoOtg15MpvyFMTEPoQysPHTigIugTzG5Gowy9OoXPPEdMPTXwykwAUVqxi2KFUwKMgUWKRYYpWhchn/wGGCxAU5BkLWLeQegrATW8cJWVTUB9Q6YqlsqrI4f/P3iEW+zvpNus8Nb3wAVM6sEA25E9wLKwOreHyU8gycf405pwdavwZ3S7ZIAP5+EOyUbUvoUuFxQxeLY76FSjjj+WC+giK6kFa4IWxVVYMr3vQ5ErgzG965+ceZxyqErhXpjUsEAZTqwDCzTE36JzYxx/tZzYFS2DkpzdMHZiRcF7866L5DjFhJE7IhRx8CNfi135COg7EBFdQig4izvJSwdLU80t/7k1SuOamfTCk2h0ruvDKz0HvHDuRGkLtx2OpistXZlw1C7+T2w3xBWwV9yuKpKhh0xApuIYJ+gZDJgVdUGaMQAQD0HfN2w59Wmnxu5kwOoxaBQEfJbRQtlYBWtq/v4IJdyjbx9b1Pzu3uvxXB4QbCmqT4MnqmiBgYL4Mq4aZ7diQleLPokhKM37P5G453mKTrkzcCyi6OuY5KLFpeBVbSu7seDPLKuCffZw5rfjU6OJ2LjIQyFm2S7E2B7MRBI/OmlbwzdbEpXQPWyjmfyFjL+/4JPu45FLkyEAAAAAElFTkSuQmCC'); + background-image: url('code-icon.svg'); background-size: 24px; background-repeat: no-repeat; background-position: left center; diff --git a/extensions/github-authentication/media/code-icon.svg b/extensions/github-authentication/media/code-icon.svg new file mode 100644 index 00000000000..cc61f81ea5a --- /dev/null +++ b/extensions/github-authentication/media/code-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/github-authentication/media/favicon.ico b/extensions/github-authentication/media/favicon.ico index 7d1a59f7bda..f9f0de3eb46 100644 Binary files a/extensions/github-authentication/media/favicon.ico and b/extensions/github-authentication/media/favicon.ico differ diff --git a/extensions/github-authentication/media/index.html b/extensions/github-authentication/media/index.html index efd092d6dfb..0ed6b27e961 100644 --- a/extensions/github-authentication/media/index.html +++ b/extensions/github-authentication/media/index.html @@ -10,12 +10,12 @@ - - Visual Studio Code - +
- You are signed in now and can close this page. + Sign-in successful! Returning to ... +

+ If you're not redirected automatically, click here or close this page.
An error occurred while signing in: @@ -23,13 +23,26 @@
diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index 8920ea5d357..47107db7069 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -121,356 +121,365 @@ async function exchangeCodeForToken( } } -const allFlows: IFlow[] = [ - new class UrlHandlerFlow implements IFlow { - label = l10n.t('url handler'); - options: IFlowOptions = { - supportsGitHubDotCom: true, - // Supporting GHES would be challenging because different versions - // used a different client ID. We could try to detect the version - // and use the right one, but that's a lot of work when we have - // other flows that work well. - supportsGitHubEnterpriseServer: false, - supportsHostedGitHubEnterprise: true, - supportsRemoteExtensionHost: true, - supportsWebWorkerExtensionHost: true, - // exchanging a code for a token requires a client secret - supportsNoClientSecret: false, - supportsSupportedClients: true, - supportsUnsupportedClients: false - }; +class UrlHandlerFlow implements IFlow { + label = l10n.t('url handler'); + options: IFlowOptions = { + supportsGitHubDotCom: true, + // Supporting GHES would be challenging because different versions + // used a different client ID. We could try to detect the version + // and use the right one, but that's a lot of work when we have + // other flows that work well. + supportsGitHubEnterpriseServer: false, + supportsHostedGitHubEnterprise: true, + supportsRemoteExtensionHost: true, + supportsWebWorkerExtensionHost: true, + // exchanging a code for a token requires a client secret + supportsNoClientSecret: false, + supportsSupportedClients: true, + supportsUnsupportedClients: false + }; - async trigger({ - scopes, - baseUri, - redirectUri, - logger, - nonce, - callbackUri, - uriHandler, - enterpriseUri, - existingLogin - }: IFlowTriggerOptions): Promise { - logger.info(`Trying without local server... (${scopes})`); - return await window.withProgress({ - location: ProgressLocation.Notification, - title: l10n.t({ - message: 'Signing in to {0}...', - args: [baseUri.authority], - comment: ['The {0} will be a url, e.g. github.com'] - }), - cancellable: true - }, async (_, token) => { - const promise = uriHandler.waitForCode(logger, scopes, nonce, token); + async trigger({ + scopes, + baseUri, + redirectUri, + logger, + nonce, + callbackUri, + uriHandler, + enterpriseUri, + existingLogin + }: IFlowTriggerOptions): Promise { + logger.info(`Trying without local server... (${scopes})`); + return await window.withProgress({ + location: ProgressLocation.Notification, + title: l10n.t({ + message: 'Signing in to {0}...', + args: [baseUri.authority], + comment: ['The {0} will be a url, e.g. github.com'] + }), + cancellable: true + }, async (_, token) => { + const promise = uriHandler.waitForCode(logger, scopes, nonce, token); - const searchParams = new URLSearchParams([ - ['client_id', Config.gitHubClientId], - ['redirect_uri', redirectUri.toString(true)], - ['scope', scopes], - ['state', encodeURIComponent(callbackUri.toString(true))] - ]); - if (existingLogin) { - searchParams.append('login', existingLogin); - } else { - searchParams.append('prompt', 'select_account'); - } - - // The extra toString, parse is apparently needed for env.openExternal - // to open the correct URL. - const uri = Uri.parse(baseUri.with({ - path: '/login/oauth/authorize', - query: searchParams.toString() - }).toString(true)); - await env.openExternal(uri); - - const code = await promise; - - const proxyEndpoints: { [providerId: string]: string } | undefined = await commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); - const endpointUrl = proxyEndpoints?.github - ? Uri.parse(`${proxyEndpoints.github}login/oauth/access_token`) - : baseUri.with({ path: '/login/oauth/access_token' }); - - const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, enterpriseUri); - return accessToken; - }); - } - }, - new class LocalServerFlow implements IFlow { - label = l10n.t('local server'); - options: IFlowOptions = { - supportsGitHubDotCom: true, - // Supporting GHES would be challenging because different versions - // used a different client ID. We could try to detect the version - // and use the right one, but that's a lot of work when we have - // other flows that work well. - supportsGitHubEnterpriseServer: false, - supportsHostedGitHubEnterprise: true, - // Opening a port on the remote side can't be open in the browser on - // the client side so this flow won't work in remote extension hosts - supportsRemoteExtensionHost: false, - // Web worker can't open a port to listen for the redirect - supportsWebWorkerExtensionHost: false, - // exchanging a code for a token requires a client secret - supportsNoClientSecret: false, - supportsSupportedClients: true, - supportsUnsupportedClients: true - }; - async trigger({ - scopes, - baseUri, - redirectUri, - logger, - enterpriseUri, - existingLogin - }: IFlowTriggerOptions): Promise { - logger.info(`Trying with local server... (${scopes})`); - return await window.withProgress({ - location: ProgressLocation.Notification, - title: l10n.t({ - message: 'Signing in to {0}...', - args: [baseUri.authority], - comment: ['The {0} will be a url, e.g. github.com'] - }), - cancellable: true - }, async (_, token) => { - const searchParams = new URLSearchParams([ - ['client_id', Config.gitHubClientId], - ['redirect_uri', redirectUri.toString(true)], - ['scope', scopes], - ]); - if (existingLogin) { - searchParams.append('login', existingLogin); - } else { - searchParams.append('prompt', 'select_account'); - } - - const loginUrl = baseUri.with({ - path: '/login/oauth/authorize', - query: searchParams.toString() - }); - const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true)); - const port = await server.start(); - - let codeToExchange; - try { - env.openExternal(Uri.parse(`http://127.0.0.1:${port}/signin?nonce=${encodeURIComponent(server.nonce)}`)); - const { code } = await Promise.race([ - server.waitForOAuthResponse(), - new Promise((_, reject) => setTimeout(() => reject(TIMED_OUT_ERROR), 300_000)), // 5min timeout - promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject(USER_CANCELLATION_ERROR); }).promise - ]); - codeToExchange = code; - } finally { - setTimeout(() => { - void server.stop(); - }, 5000); - } - - const accessToken = await exchangeCodeForToken( - logger, - baseUri.with({ path: '/login/oauth/access_token' }), - redirectUri, - codeToExchange, - enterpriseUri); - return accessToken; - }); - } - }, - new class DeviceCodeFlow implements IFlow { - label = l10n.t('device code'); - options: IFlowOptions = { - supportsGitHubDotCom: true, - supportsGitHubEnterpriseServer: true, - supportsHostedGitHubEnterprise: true, - supportsRemoteExtensionHost: true, - // CORS prevents this from working in web workers - supportsWebWorkerExtensionHost: false, - supportsNoClientSecret: true, - supportsSupportedClients: true, - supportsUnsupportedClients: true - }; - async trigger({ scopes, baseUri, logger }: IFlowTriggerOptions) { - logger.info(`Trying device code flow... (${scopes})`); - - // Get initial device code - const uri = baseUri.with({ - path: '/login/device/code', - query: `client_id=${Config.gitHubClientId}&scope=${scopes}` - }); - const result = await fetching(uri.toString(true), { - method: 'POST', - headers: { - Accept: 'application/json' - } - }); - if (!result.ok) { - throw new Error(`Failed to get one-time code: ${await result.text()}`); + const searchParams = new URLSearchParams([ + ['client_id', Config.gitHubClientId], + ['redirect_uri', redirectUri.toString(true)], + ['scope', scopes], + ['state', encodeURIComponent(callbackUri.toString(true))] + ]); + if (existingLogin) { + searchParams.append('login', existingLogin); + } else { + searchParams.append('prompt', 'select_account'); } - const json = await result.json() as IGitHubDeviceCodeResponse; + // The extra toString, parse is apparently needed for env.openExternal + // to open the correct URL. + const uri = Uri.parse(baseUri.with({ + path: '/login/oauth/authorize', + query: searchParams.toString() + }).toString(true)); + await env.openExternal(uri); - const button = l10n.t('Copy & Continue to GitHub'); - const modalResult = await window.showInformationMessage( - l10n.t({ message: 'Your Code: {0}', args: [json.user_code], comment: ['The {0} will be a code, e.g. 123-456'] }), - { - modal: true, - detail: l10n.t('To finish authenticating, navigate to GitHub and paste in the above one-time code.') - }, button); + const code = await promise; - if (modalResult !== button) { - throw new Error(USER_CANCELLATION_ERROR); + const proxyEndpoints: { [providerId: string]: string } | undefined = await commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); + const endpointUrl = proxyEndpoints?.github + ? Uri.parse(`${proxyEndpoints.github}login/oauth/access_token`) + : baseUri.with({ path: '/login/oauth/access_token' }); + + const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, enterpriseUri); + return accessToken; + }); + } +} + +class LocalServerFlow implements IFlow { + label = l10n.t('local server'); + options: IFlowOptions = { + supportsGitHubDotCom: true, + // Supporting GHES would be challenging because different versions + // used a different client ID. We could try to detect the version + // and use the right one, but that's a lot of work when we have + // other flows that work well. + supportsGitHubEnterpriseServer: false, + supportsHostedGitHubEnterprise: true, + // Opening a port on the remote side can't be open in the browser on + // the client side so this flow won't work in remote extension hosts + supportsRemoteExtensionHost: false, + // Web worker can't open a port to listen for the redirect + supportsWebWorkerExtensionHost: false, + // exchanging a code for a token requires a client secret + supportsNoClientSecret: false, + supportsSupportedClients: true, + supportsUnsupportedClients: true + }; + async trigger({ + scopes, + baseUri, + redirectUri, + logger, + callbackUri, + enterpriseUri, + existingLogin + }: IFlowTriggerOptions): Promise { + logger.info(`Trying with local server... (${scopes})`); + return await window.withProgress({ + location: ProgressLocation.Notification, + title: l10n.t({ + message: 'Signing in to {0}...', + args: [baseUri.authority], + comment: ['The {0} will be a url, e.g. github.com'] + }), + cancellable: true + }, async (_, token) => { + const searchParams = new URLSearchParams([ + ['client_id', Config.gitHubClientId], + ['redirect_uri', redirectUri.toString(true)], + ['scope', scopes], + ]); + if (existingLogin) { + searchParams.append('login', existingLogin); + } else { + searchParams.append('prompt', 'select_account'); } - await env.clipboard.writeText(json.user_code); - - const uriToOpen = await env.asExternalUri(Uri.parse(json.verification_uri)); - await env.openExternal(uriToOpen); - - return await this.waitForDeviceCodeAccessToken(baseUri, json); - } - - private async waitForDeviceCodeAccessToken( - baseUri: Uri, - json: IGitHubDeviceCodeResponse, - ): Promise { - return await window.withProgress({ - location: ProgressLocation.Notification, - cancellable: true, - title: l10n.t({ - message: 'Open [{0}]({0}) in a new tab and paste your one-time code: {1}', - args: [json.verification_uri, json.user_code], - comment: [ - 'The [{0}]({0}) will be a url and the {1} will be a code, e.g. 123-456', - '{Locked="[{0}]({0})"}' - ] - }) - }, async (_, token) => { - const refreshTokenUri = baseUri.with({ - path: '/login/oauth/access_token', - query: `client_id=${Config.gitHubClientId}&device_code=${json.device_code}&grant_type=urn:ietf:params:oauth:grant-type:device_code` - }); - - // Try for 2 minutes - const attempts = 120 / json.interval; - for (let i = 0; i < attempts; i++) { - await new Promise(resolve => setTimeout(resolve, json.interval * 1000)); - if (token.isCancellationRequested) { - throw new Error(USER_CANCELLATION_ERROR); - } - let accessTokenResult; - try { - accessTokenResult = await fetching(refreshTokenUri.toString(true), { - method: 'POST', - headers: { - Accept: 'application/json' - } - }); - } catch { - continue; - } - - if (!accessTokenResult.ok) { - continue; - } - - const accessTokenJson = await accessTokenResult.json(); - - if (accessTokenJson.error === 'authorization_pending') { - continue; - } - - if (accessTokenJson.error) { - throw new Error(accessTokenJson.error_description); - } - - return accessTokenJson.access_token; - } - - throw new Error(TIMED_OUT_ERROR); + const loginUrl = baseUri.with({ + path: '/login/oauth/authorize', + query: searchParams.toString() }); - } - }, - new class PatFlow implements IFlow { - label = l10n.t('personal access token'); - options: IFlowOptions = { - supportsGitHubDotCom: true, - supportsGitHubEnterpriseServer: true, - supportsHostedGitHubEnterprise: true, - supportsRemoteExtensionHost: true, - supportsWebWorkerExtensionHost: true, - supportsNoClientSecret: true, - // PATs can't be used with Settings Sync so we don't enable this flow - // for supported clients - supportsSupportedClients: false, - supportsUnsupportedClients: true - }; + const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true), callbackUri.toString(true)); + const port = await server.start(); - async trigger({ scopes, baseUri, logger, enterpriseUri }: IFlowTriggerOptions) { - logger.info(`Trying to retrieve PAT... (${scopes})`); - - const button = l10n.t('Continue to GitHub'); - const modalResult = await window.showInformationMessage( - l10n.t('Continue to GitHub to create a Personal Access Token (PAT)'), - { - modal: true, - detail: l10n.t('To finish authenticating, navigate to GitHub to create a PAT then paste the PAT into the input box.') - }, button); - - if (modalResult !== button) { - throw new Error(USER_CANCELLATION_ERROR); - } - - const description = `${env.appName} (${scopes})`; - const uriToOpen = await env.asExternalUri(baseUri.with({ path: '/settings/tokens/new', query: `description=${description}&scopes=${scopes.split(' ').join(',')}` })); - await env.openExternal(uriToOpen); - const token = await window.showInputBox({ placeHolder: `ghp_1a2b3c4...`, prompt: `GitHub Personal Access Token - ${scopes}`, ignoreFocusOut: true }); - if (!token) { throw new Error(USER_CANCELLATION_ERROR); } - - const appUri = !enterpriseUri || isHostedGitHubEnterprise(enterpriseUri) - ? Uri.parse(`${baseUri.scheme}://api.${baseUri.authority}`) - : Uri.parse(`${baseUri.scheme}://${baseUri.authority}/api/v3`); - - const tokenScopes = await this.getScopes(token, appUri, logger); // Example: ['repo', 'user'] - const scopesList = scopes.split(' '); // Example: 'read:user repo user:email' - if (!scopesList.every(scope => { - const included = tokenScopes.includes(scope); - if (included || !scope.includes(':')) { - return included; - } - - return scope.split(':').some(splitScopes => { - return tokenScopes.includes(splitScopes); - }); - })) { - throw new Error(`The provided token does not match the requested scopes: ${scopes}`); - } - - return token; - } - - private async getScopes(token: string, serverUri: Uri, logger: Log): Promise { + let codeToExchange; try { - logger.info('Getting token scopes...'); - const result = await fetching(serverUri.toString(), { - headers: { - Authorization: `token ${token}`, - 'User-Agent': `${env.appName} (${env.appHost})` - } - }); - - if (result.ok) { - const scopes = result.headers.get('X-OAuth-Scopes'); - return scopes ? scopes.split(',').map(scope => scope.trim()) : []; - } else { - logger.error(`Getting scopes failed: ${result.statusText}`); - throw new Error(result.statusText); - } - } catch (ex) { - logger.error(ex.message); - throw new Error(NETWORK_ERROR); + env.openExternal(Uri.parse(`http://127.0.0.1:${port}/signin?nonce=${encodeURIComponent(server.nonce)}`)); + const { code } = await Promise.race([ + server.waitForOAuthResponse(), + new Promise((_, reject) => setTimeout(() => reject(TIMED_OUT_ERROR), 300_000)), // 5min timeout + promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject(USER_CANCELLATION_ERROR); }).promise + ]); + codeToExchange = code; + } finally { + setTimeout(() => { + void server.stop(); + }, 5000); } + + const accessToken = await exchangeCodeForToken( + logger, + baseUri.with({ path: '/login/oauth/access_token' }), + redirectUri, + codeToExchange, + enterpriseUri); + return accessToken; + }); + } +} + +class DeviceCodeFlow implements IFlow { + label = l10n.t('device code'); + options: IFlowOptions = { + supportsGitHubDotCom: true, + supportsGitHubEnterpriseServer: true, + supportsHostedGitHubEnterprise: true, + supportsRemoteExtensionHost: true, + // CORS prevents this from working in web workers + supportsWebWorkerExtensionHost: false, + supportsNoClientSecret: true, + supportsSupportedClients: true, + supportsUnsupportedClients: true + }; + async trigger({ scopes, baseUri, logger }: IFlowTriggerOptions) { + logger.info(`Trying device code flow... (${scopes})`); + + // Get initial device code + const uri = baseUri.with({ + path: '/login/device/code', + query: `client_id=${Config.gitHubClientId}&scope=${scopes}` + }); + const result = await fetching(uri.toString(true), { + method: 'POST', + headers: { + Accept: 'application/json' + } + }); + if (!result.ok) { + throw new Error(`Failed to get one-time code: ${await result.text()}`); + } + + const json = await result.json() as IGitHubDeviceCodeResponse; + + const button = l10n.t('Copy & Continue to GitHub'); + const modalResult = await window.showInformationMessage( + l10n.t({ message: 'Your Code: {0}', args: [json.user_code], comment: ['The {0} will be a code, e.g. 123-456'] }), + { + modal: true, + detail: l10n.t('To finish authenticating, navigate to GitHub and paste in the above one-time code.') + }, button); + + if (modalResult !== button) { + throw new Error(USER_CANCELLATION_ERROR); + } + + await env.clipboard.writeText(json.user_code); + + const uriToOpen = await env.asExternalUri(Uri.parse(json.verification_uri)); + await env.openExternal(uriToOpen); + + return await this.waitForDeviceCodeAccessToken(baseUri, json); + } + + private async waitForDeviceCodeAccessToken( + baseUri: Uri, + json: IGitHubDeviceCodeResponse, + ): Promise { + return await window.withProgress({ + location: ProgressLocation.Notification, + cancellable: true, + title: l10n.t({ + message: 'Open [{0}]({0}) in a new tab and paste your one-time code: {1}', + args: [json.verification_uri, json.user_code], + comment: [ + 'The [{0}]({0}) will be a url and the {1} will be a code, e.g. 123-456', + '{Locked="[{0}]({0})"}' + ] + }) + }, async (_, token) => { + const refreshTokenUri = baseUri.with({ + path: '/login/oauth/access_token', + query: `client_id=${Config.gitHubClientId}&device_code=${json.device_code}&grant_type=urn:ietf:params:oauth:grant-type:device_code` + }); + + // Try for 2 minutes + const attempts = 120 / json.interval; + for (let i = 0; i < attempts; i++) { + await new Promise(resolve => setTimeout(resolve, json.interval * 1000)); + if (token.isCancellationRequested) { + throw new Error(USER_CANCELLATION_ERROR); + } + let accessTokenResult; + try { + accessTokenResult = await fetching(refreshTokenUri.toString(true), { + method: 'POST', + headers: { + Accept: 'application/json' + } + }); + } catch { + continue; + } + + if (!accessTokenResult.ok) { + continue; + } + + const accessTokenJson = await accessTokenResult.json(); + + if (accessTokenJson.error === 'authorization_pending') { + continue; + } + + if (accessTokenJson.error) { + throw new Error(accessTokenJson.error_description); + } + + return accessTokenJson.access_token; + } + + throw new Error(TIMED_OUT_ERROR); + }); + } +} + +class PatFlow implements IFlow { + label = l10n.t('personal access token'); + options: IFlowOptions = { + supportsGitHubDotCom: true, + supportsGitHubEnterpriseServer: true, + supportsHostedGitHubEnterprise: true, + supportsRemoteExtensionHost: true, + supportsWebWorkerExtensionHost: true, + supportsNoClientSecret: true, + // PATs can't be used with Settings Sync so we don't enable this flow + // for supported clients + supportsSupportedClients: false, + supportsUnsupportedClients: true + }; + + async trigger({ scopes, baseUri, logger, enterpriseUri }: IFlowTriggerOptions) { + logger.info(`Trying to retrieve PAT... (${scopes})`); + + const button = l10n.t('Continue to GitHub'); + const modalResult = await window.showInformationMessage( + l10n.t('Continue to GitHub to create a Personal Access Token (PAT)'), + { + modal: true, + detail: l10n.t('To finish authenticating, navigate to GitHub to create a PAT then paste the PAT into the input box.') + }, button); + + if (modalResult !== button) { + throw new Error(USER_CANCELLATION_ERROR); + } + + const description = `${env.appName} (${scopes})`; + const uriToOpen = await env.asExternalUri(baseUri.with({ path: '/settings/tokens/new', query: `description=${description}&scopes=${scopes.split(' ').join(',')}` })); + await env.openExternal(uriToOpen); + const token = await window.showInputBox({ placeHolder: `ghp_1a2b3c4...`, prompt: `GitHub Personal Access Token - ${scopes}`, ignoreFocusOut: true }); + if (!token) { throw new Error(USER_CANCELLATION_ERROR); } + + const appUri = !enterpriseUri || isHostedGitHubEnterprise(enterpriseUri) + ? Uri.parse(`${baseUri.scheme}://api.${baseUri.authority}`) + : Uri.parse(`${baseUri.scheme}://${baseUri.authority}/api/v3`); + + const tokenScopes = await this.getScopes(token, appUri, logger); // Example: ['repo', 'user'] + const scopesList = scopes.split(' '); // Example: 'read:user repo user:email' + if (!scopesList.every(scope => { + const included = tokenScopes.includes(scope); + if (included || !scope.includes(':')) { + return included; + } + + return scope.split(':').some(splitScopes => { + return tokenScopes.includes(splitScopes); + }); + })) { + throw new Error(`The provided token does not match the requested scopes: ${scopes}`); + } + + return token; + } + + private async getScopes(token: string, serverUri: Uri, logger: Log): Promise { + try { + logger.info('Getting token scopes...'); + const result = await fetching(serverUri.toString(), { + headers: { + Authorization: `token ${token}`, + 'User-Agent': `${env.appName} (${env.appHost})` + } + }); + + if (result.ok) { + const scopes = result.headers.get('X-OAuth-Scopes'); + return scopes ? scopes.split(',').map(scope => scope.trim()) : []; + } else { + logger.error(`Getting scopes failed: ${result.statusText}`); + throw new Error(result.statusText); + } + } catch (ex) { + logger.error(ex.message); + throw new Error(NETWORK_ERROR); } } +} + +const allFlows: IFlow[] = [ + new LocalServerFlow(), + new UrlHandlerFlow(), + new DeviceCodeFlow(), + new PatFlow() ]; export function getFlows(query: IFlowQuery) { diff --git a/extensions/github-authentication/src/node/authServer.ts b/extensions/github-authentication/src/node/authServer.ts index de08c6fca0f..0bc2768826d 100644 --- a/extensions/github-authentication/src/node/authServer.ts +++ b/extensions/github-authentication/src/node/authServer.ts @@ -7,17 +7,22 @@ import { URL } from 'url'; import * as fs from 'fs'; import * as path from 'path'; import { randomBytes } from 'crypto'; +import { env } from 'vscode'; function sendFile(res: http.ServerResponse, filepath: string) { + const isSvg = filepath.endsWith('.svg'); fs.readFile(filepath, (err, body) => { if (err) { console.error(err); res.writeHead(404); res.end(); } else { - res.writeHead(200, { - 'content-length': body.length, - }); + if (isSvg) { + // SVGs need to be served with the correct content type + res.setHeader('Content-Type', 'image/svg+xml'); + } + res.setHeader('content-length', body.length); + res.writeHead(200); res.end(body); } }); @@ -82,7 +87,7 @@ export class LoopbackAuthServer implements ILoopbackServer { return this._startingRedirect.searchParams.get('state') ?? undefined; } - constructor(serveRoot: string, startingRedirect: string) { + constructor(serveRoot: string, startingRedirect: string, callbackUri: string) { if (!serveRoot) { throw new Error('serveRoot must be defined'); } @@ -93,13 +98,14 @@ export class LoopbackAuthServer implements ILoopbackServer { let deferred: { resolve: (result: IOAuthResult) => void; reject: (reason: any) => void }; this._resultPromise = new Promise((resolve, reject) => deferred = { resolve, reject }); + const appNameQueryParam = `&app_name=${encodeURIComponent(env.appName)}`; this._server = http.createServer((req, res) => { const reqUrl = new URL(req.url!, `http://${req.headers.host}`); switch (reqUrl.pathname) { case '/signin': { const receivedNonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+'); if (receivedNonce !== this.nonce) { - res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` }); + res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}${appNameQueryParam}` }); res.end(); } res.writeHead(302, { location: this._startingRedirect.toString() }); @@ -116,17 +122,17 @@ export class LoopbackAuthServer implements ILoopbackServer { return; } if (this.state !== state) { - res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}` }); + res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}${appNameQueryParam}` }); res.end(); throw new Error('State does not match.'); } if (this.nonce !== nonce) { - res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` }); + res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}${appNameQueryParam}` }); res.end(); throw new Error('Nonce does not match.'); } deferred.resolve({ code, state }); - res.writeHead(302, { location: '/' }); + res.writeHead(302, { location: `/?redirect_uri=${encodeURIComponent(callbackUri)}${appNameQueryParam}` }); res.end(); break; } diff --git a/extensions/github-authentication/src/test/flows.test.ts b/extensions/github-authentication/src/test/flows.test.ts index 7f4963f4bd5..77c023e4819 100644 --- a/extensions/github-authentication/src/test/flows.test.ts +++ b/extensions/github-authentication/src/test/flows.test.ts @@ -34,8 +34,8 @@ suite('getFlows', () => { target: GitHubTarget.DotCom }, expectedFlows: [ - Flows.UrlHandlerFlow, Flows.LocalServerFlow, + Flows.UrlHandlerFlow, Flows.DeviceCodeFlow ] }, @@ -47,8 +47,8 @@ suite('getFlows', () => { target: GitHubTarget.HostedEnterprise }, expectedFlows: [ - Flows.UrlHandlerFlow, Flows.LocalServerFlow, + Flows.UrlHandlerFlow, Flows.DeviceCodeFlow, Flows.PatFlow ] diff --git a/extensions/github-authentication/src/test/node/authServer.test.ts b/extensions/github-authentication/src/test/node/authServer.test.ts index 6de8da61fda..e7fdf6139bb 100644 --- a/extensions/github-authentication/src/test/node/authServer.test.ts +++ b/extensions/github-authentication/src/test/node/authServer.test.ts @@ -5,13 +5,14 @@ import * as assert from 'assert'; import { LoopbackAuthServer } from '../../node/authServer'; +import { env } from 'vscode'; suite('LoopbackAuthServer', () => { let server: LoopbackAuthServer; let port: number; setup(async () => { - server = new LoopbackAuthServer(__dirname, 'http://localhost:8080'); + server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com'); port = await server.start(); }); @@ -53,7 +54,7 @@ suite('LoopbackAuthServer', () => { { redirect: 'manual' } ); assert.strictEqual(response.status, 302); - assert.strictEqual(response.headers.get('location'), '/'); + assert.strictEqual(response.headers.get('location'), `/?redirect_uri=https%3A%2F%2Fcode.visualstudio.com&app_name=${encodeURIComponent(env.appName)}`); await Promise.race([ server.waitForOAuthResponse().then(result => { assert.strictEqual(result.code, 'valid-code'); diff --git a/extensions/github/src/auth.ts b/extensions/github/src/auth.ts index d5487643823..56d68e287ee 100644 --- a/extensions/github/src/auth.ts +++ b/extensions/github/src/auth.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AuthenticationSession, authentication, window } from 'vscode'; +import { AuthenticationSession, EventEmitter, authentication, window } from 'vscode'; import { Agent, globalAgent } from 'https'; import { graphql } from '@octokit/graphql/types'; import { Octokit } from '@octokit/rest'; import { httpsOverHttp } from 'tunnel'; import { URL } from 'url'; +import { DisposableStore, sequentialize } from './util.js'; export class AuthenticationError extends Error { } @@ -57,35 +58,58 @@ export function getOctokit(): Promise { return _octokit; } -let _octokitGraphql: graphql | undefined; +export class OctokitService { + private _octokitGraphql: graphql | undefined; -export async function getOctokitGraphql(): Promise { - if (!_octokitGraphql) { - try { - const session = await authentication.getSession('github', scopes, { silent: true }); + private readonly _onDidChangeSessions = new EventEmitter(); + readonly onDidChangeSessions = this._onDidChangeSessions.event; - if (!session) { - throw new AuthenticationError('No GitHub authentication session available.'); + private readonly _disposables = new DisposableStore(); + + constructor() { + this._disposables.add(this._onDidChangeSessions); + this._disposables.add(authentication.onDidChangeSessions(e => { + if (e.provider.id === 'github') { + this._octokitGraphql = undefined; + this._onDidChangeSessions.fire(); } - - const token = session.accessToken; - const { graphql } = await import('@octokit/graphql'); - - _octokitGraphql = graphql.defaults({ - headers: { - authorization: `token ${token}` - }, - request: { - agent: getAgent() - } - }); - - return _octokitGraphql; - } catch (err) { - _octokitGraphql = undefined; - throw new AuthenticationError(err.message); - } + })); } - return _octokitGraphql; + @sequentialize + public async getOctokitGraphql(): Promise { + if (!this._octokitGraphql) { + try { + const session = await authentication.getSession('github', scopes, { silent: true }); + + if (!session) { + throw new AuthenticationError('No GitHub authentication session available.'); + } + + const token = session.accessToken; + const { graphql } = await import('@octokit/graphql'); + + this._octokitGraphql = graphql.defaults({ + headers: { + authorization: `token ${token}` + }, + request: { + agent: getAgent() + } + }); + + return this._octokitGraphql; + } catch (err) { + this._octokitGraphql = undefined; + throw new AuthenticationError(err.message); + } + } + + return this._octokitGraphql; + } + + dispose(): void { + this._octokitGraphql = undefined; + this._disposables.dispose(); + } } diff --git a/extensions/github/src/branchProtection.ts b/extensions/github/src/branchProtection.ts index 7b56f5aab40..5cd1f2f89fa 100644 --- a/extensions/github/src/branchProtection.ts +++ b/extensions/github/src/branchProtection.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { authentication, EventEmitter, LogOutputChannel, Memento, Uri, workspace } from 'vscode'; +import { EventEmitter, LogOutputChannel, Memento, Uri, workspace } from 'vscode'; import { Repository as GitHubRepository, RepositoryRuleset } from '@octokit/graphql-schema'; -import { AuthenticationError, getOctokitGraphql } from './auth.js'; +import { AuthenticationError, OctokitService } from './auth.js'; import { API, BranchProtection, BranchProtectionProvider, BranchProtectionRule, Repository } from './typings/git.js'; import { DisposableStore, getRepositoryFromUrl } from './util.js'; import { TelemetryReporter } from '@vscode/extension-telemetry'; @@ -61,7 +61,7 @@ export class GitHubBranchProtectionProviderManager { if (enabled) { for (const repository of this.gitAPI.repositories) { - this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GitHubBranchProtectionProvider(repository, this.globalState, this.logger, this.telemetryReporter))); + this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GitHubBranchProtectionProvider(repository, this.globalState, this.octokitService, this.logger, this.telemetryReporter))); } } else { this.providerDisposables.dispose(); @@ -73,11 +73,13 @@ export class GitHubBranchProtectionProviderManager { constructor( private readonly gitAPI: API, private readonly globalState: Memento, + private readonly octokitService: OctokitService, private readonly logger: LogOutputChannel, private readonly telemetryReporter: TelemetryReporter) { this.disposables.add(this.gitAPI.onDidOpenRepository(repository => { if (this._enabled) { - this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GitHubBranchProtectionProvider(repository, this.globalState, this.logger, this.telemetryReporter))); + this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, + new GitHubBranchProtectionProvider(repository, this.globalState, this.octokitService, this.logger, this.telemetryReporter))); } })); @@ -109,20 +111,24 @@ export class GitHubBranchProtectionProvider implements BranchProtectionProvider private branchProtection: BranchProtection[]; private readonly globalStateKey = `branchProtection:${this.repository.rootUri.toString()}`; + private readonly disposables = new DisposableStore(); + constructor( private readonly repository: Repository, private readonly globalState: Memento, + private readonly octokitService: OctokitService, private readonly logger: LogOutputChannel, - private readonly telemetryReporter: TelemetryReporter) { + private readonly telemetryReporter: TelemetryReporter + ) { + this.disposables.add(this._onDidChangeBranchProtection); + // Restore branch protection from global state this.branchProtection = this.globalState.get(this.globalStateKey, []); repository.status().then(() => { - authentication.onDidChangeSessions(e => { - if (e.provider.id === 'github') { - this.updateRepositoryBranchProtection(); - } - }); + this.disposables.add(this.octokitService.onDidChangeSessions(() => { + this.updateRepositoryBranchProtection(); + })); this.updateRepositoryBranchProtection(); }); } @@ -132,7 +138,7 @@ export class GitHubBranchProtectionProvider implements BranchProtectionProvider } private async getRepositoryDetails(owner: string, repo: string): Promise { - const graphql = await getOctokitGraphql(); + const graphql = await this.octokitService.getOctokitGraphql(); const { repository } = await graphql<{ repository: GitHubRepository }>(REPOSITORY_QUERY, { owner, repo }); return repository; @@ -142,7 +148,7 @@ export class GitHubBranchProtectionProvider implements BranchProtectionProvider const rulesets: RepositoryRuleset[] = []; let cursor: string | undefined = undefined; - const graphql = await getOctokitGraphql(); + const graphql = await this.octokitService.getOctokitGraphql(); while (true) { const { repository } = await graphql<{ repository: GitHubRepository }>(REPOSITORY_RULESETS_QUERY, { owner, repo, cursor }); @@ -241,4 +247,8 @@ export class GitHubBranchProtectionProvider implements BranchProtectionProvider return refName; } } + + dispose(): void { + this.disposables.dispose(); + } } diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index bf07a0b9278..17906c57d44 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -17,6 +17,7 @@ import { GitHubBranchProtectionProviderManager } from './branchProtection.js'; import { GitHubCanonicalUriProvider } from './canonicalUriProvider.js'; import { VscodeDevShareProvider } from './shareProviders.js'; import { GitHubSourceControlHistoryItemDetailsProvider } from './historyItemDetailsProvider.js'; +import { OctokitService } from './auth.js'; export function activate(context: ExtensionContext): void { const disposables: Disposable[] = []; @@ -35,8 +36,11 @@ export function activate(context: ExtensionContext): void { const telemetryReporter = new TelemetryReporter(aiKey); disposables.push(telemetryReporter); + const octokitService = new OctokitService(); + disposables.push(octokitService); + disposables.push(initializeGitBaseExtension()); - disposables.push(initializeGitExtension(context, telemetryReporter, logger)); + disposables.push(initializeGitExtension(context, octokitService, telemetryReporter, logger)); } function initializeGitBaseExtension(): Disposable { @@ -84,7 +88,7 @@ function setGitHubContext(gitAPI: API, disposables: DisposableStore) { } } -function initializeGitExtension(context: ExtensionContext, telemetryReporter: TelemetryReporter, logger: LogOutputChannel): Disposable { +function initializeGitExtension(context: ExtensionContext, octokitService: OctokitService, telemetryReporter: TelemetryReporter, logger: LogOutputChannel): Disposable { const disposables = new DisposableStore(); let gitExtension = extensions.getExtension('vscode.git'); @@ -98,10 +102,10 @@ function initializeGitExtension(context: ExtensionContext, telemetryReporter: Te disposables.add(registerCommands(gitAPI)); disposables.add(new GithubCredentialProviderManager(gitAPI)); - disposables.add(new GitHubBranchProtectionProviderManager(gitAPI, context.globalState, logger, telemetryReporter)); + disposables.add(new GitHubBranchProtectionProviderManager(gitAPI, context.globalState, octokitService, logger, telemetryReporter)); disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler(telemetryReporter))); disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI))); - disposables.add(gitAPI.registerSourceControlHistoryItemDetailsProvider(new GitHubSourceControlHistoryItemDetailsProvider(gitAPI, logger))); + disposables.add(gitAPI.registerSourceControlHistoryItemDetailsProvider(new GitHubSourceControlHistoryItemDetailsProvider(gitAPI, octokitService, logger))); disposables.add(new GitHubCanonicalUriProvider(gitAPI)); disposables.add(new VscodeDevShareProvider(gitAPI)); setGitHubContext(gitAPI, disposables); diff --git a/extensions/github/src/historyItemDetailsProvider.ts b/extensions/github/src/historyItemDetailsProvider.ts index 6d3a414036c..1a5d58a1c52 100644 --- a/extensions/github/src/historyItemDetailsProvider.ts +++ b/extensions/github/src/historyItemDetailsProvider.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { authentication, Command, l10n, LogOutputChannel, workspace } from 'vscode'; +import { Command, l10n, LogOutputChannel, workspace } from 'vscode'; import { Commit, Repository as GitHubRepository, Maybe } from '@octokit/graphql-schema'; import { API, AvatarQuery, AvatarQueryCommit, Repository, SourceControlHistoryItemDetailsProvider } from './typings/git.js'; import { DisposableStore, getRepositoryDefaultRemote, getRepositoryDefaultRemoteUrl, getRepositoryFromUrl, groupBy, sequentialize } from './util.js'; -import { AuthenticationError, getOctokitGraphql } from './auth.js'; +import { AuthenticationError, OctokitService } from './auth.js'; import { getAvatarLink } from './links.js'; const ISSUE_EXPRESSION = /(([A-Za-z0-9_.\-]+)\/([A-Za-z0-9_.\-]+))?(#|GH-)([1-9][0-9]*)($|\b)/g; @@ -82,13 +82,16 @@ export class GitHubSourceControlHistoryItemDetailsProvider implements SourceCont private readonly _store = new Map(); private readonly _disposables = new DisposableStore(); - constructor(private readonly _gitAPI: API, private readonly _logger: LogOutputChannel) { + constructor( + private readonly _gitAPI: API, + private readonly _octokitService: OctokitService, + private readonly _logger: LogOutputChannel + ) { this._disposables.add(this._gitAPI.onDidCloseRepository(repository => this._onDidCloseRepository(repository))); - this._disposables.add(authentication.onDidChangeSessions(e => { - if (e.provider.id === 'github') { - this._isUserAuthenticated = true; - } + this._disposables.add(this._octokitService.onDidChangeSessions(() => { + this._isUserAuthenticated = true; + this._store.clear(); })); this._disposables.add(workspace.onDidChangeConfiguration(e => { @@ -264,7 +267,7 @@ export class GitHubSourceControlHistoryItemDetailsProvider implements SourceCont this._logger.trace(`[GitHubSourceControlHistoryItemDetailsProvider][_loadAssignableUsers] Querying assignable user(s) for ${descriptor.owner}/${descriptor.repo}.`); try { - const graphql = await getOctokitGraphql(); + const graphql = await this._octokitService.getOctokitGraphql(); const { repository } = await graphql<{ repository: GitHubRepository }>(ASSIGNABLE_USERS_QUERY, descriptor); const users: GitHubUser[] = []; @@ -294,7 +297,7 @@ export class GitHubSourceControlHistoryItemDetailsProvider implements SourceCont this._logger.trace(`[GitHubSourceControlHistoryItemDetailsProvider][_getCommitAuthor] Querying commit author for ${descriptor.owner}/${descriptor.repo}/${commit}.`); try { - const graphql = await getOctokitGraphql(); + const graphql = await this._octokitService.getOctokitGraphql(); const { repository } = await graphql<{ repository: GitHubRepository }>(COMMIT_AUTHOR_QUERY, { ...descriptor, commit }); const commitAuthor = (repository.object as Commit).author; diff --git a/extensions/make/language-configuration.json b/extensions/make/language-configuration.json index 82645b84dfb..05881278a62 100644 --- a/extensions/make/language-configuration.json +++ b/extensions/make/language-configuration.json @@ -1,6 +1,9 @@ { "comments": { - "lineComment": "#" + "lineComment": { + "comment": "#", + "noIndent": true + } }, "brackets": [ [ diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index a884730a152..cba22fc48d5 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -156,7 +156,6 @@ export function scrollToRevealSourceLine(line: number, documentVersion: number, const progressInElement = line - Math.floor(line); scrollTo = previousTop + (rect.height * progressInElement); } - scrollTo = Math.abs(scrollTo) < 1 ? Math.sign(scrollTo) : scrollTo; window.scroll(window.scrollX, Math.max(1, window.scrollY + scrollTo)); } diff --git a/extensions/prompt-basics/package.json b/extensions/prompt-basics/package.json index 1a22f22d087..341f6e7f7b0 100644 --- a/extensions/prompt-basics/package.json +++ b/extensions/prompt-basics/package.json @@ -8,7 +8,9 @@ "engines": { "vscode": "^1.20.0" }, - "categories": ["Programming Languages"], + "categories": [ + "Programming Languages" + ], "contributes": { "languages": [ { @@ -80,21 +82,39 @@ "[prompt]": { "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, - "diffEditor.ignoreTrimWhitespace": false + "diffEditor.ignoreTrimWhitespace": false, + "editor.wordWrap": "on", + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off" + } }, "[instructions]": { "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, - "diffEditor.ignoreTrimWhitespace": false + "diffEditor.ignoreTrimWhitespace": false, + "editor.wordWrap": "on", + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off" + } }, "[chatmode]": { "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, - "diffEditor.ignoreTrimWhitespace": false + "diffEditor.ignoreTrimWhitespace": false, + "editor.wordWrap": "on", + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off" + } } }, "snippets": [ - { + { "language": "prompt", "path": "./snippets/prompt.code-snippets" }, diff --git a/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/extensions/terminal-suggest/src/env/pathExecutableCache.ts index ae7686865af..1ec3fb964fa 100644 --- a/extensions/terminal-suggest/src/env/pathExecutableCache.ts +++ b/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -76,7 +76,7 @@ export class PathExecutableCache implements vscode.Disposable { const promises: Promise | undefined>[] = []; const labels: Set = new Set(); for (const path of paths) { - promises.push(this._getFilesInPath(path, pathSeparator, labels)); + promises.push(this._getExecutablesInPath(path, pathSeparator, labels)); } // Merge all results @@ -96,7 +96,7 @@ export class PathExecutableCache implements vscode.Disposable { return this._cachedExes; } - private async _getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { + private async _getExecutablesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { try { const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); if (!dirExists) { @@ -106,11 +106,53 @@ export class PathExecutableCache implements vscode.Disposable { const fileResource = vscode.Uri.file(path); const files = await vscode.workspace.fs.readDirectory(fileResource); for (const [file, fileType] of files) { - const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); - if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, this._cachedWindowsExeExtensions)) { - result.add({ label: file, documentation: formattedPath, kind: vscode.TerminalCompletionItemKind.Method }); - labels.add(file); + let kind: vscode.TerminalCompletionItemKind | undefined; + let formattedPath: string | undefined; + const resource = vscode.Uri.joinPath(fileResource, file); + + // Skip unknown or directory file types early + if (fileType === vscode.FileType.Unknown || fileType === vscode.FileType.Directory) { + continue; } + + try { + const lstat = await fs.lstat(resource.fsPath); + if (lstat.isSymbolicLink()) { + try { + const symlinkRealPath = await fs.realpath(resource.fsPath); + const isExec = await isExecutable(symlinkRealPath, this._cachedWindowsExeExtensions); + if (!isExec) { + continue; + } + kind = vscode.TerminalCompletionItemKind.Method; + formattedPath = `${resource.fsPath} -> ${symlinkRealPath}`; + } catch { + continue; + } + } + } catch { + // Ignore errors for unreadable files + continue; + } + + formattedPath = formattedPath ?? getFriendlyResourcePath(resource, pathSeparator); + + // Check if already added or not executable + if (labels.has(file)) { + continue; + } + + const isExec = kind === vscode.TerminalCompletionItemKind.Method || await isExecutable(formattedPath, this._cachedWindowsExeExtensions); + if (!isExec) { + continue; + } + + result.add({ + label: file, + documentation: formattedPath, + kind: kind ?? vscode.TerminalCompletionItemKind.Method + }); + labels.add(file); } return result; } catch (e) { diff --git a/extensions/terminal-suggest/src/fig/figInterface.ts b/extensions/terminal-suggest/src/fig/figInterface.ts index cb764e4ed62..46f634c7304 100644 --- a/extensions/terminal-suggest/src/fig/figInterface.ts +++ b/extensions/terminal-suggest/src/fig/figInterface.ts @@ -31,12 +31,11 @@ export async function getFigSuggestions( specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: ICompletionResource[], - prefix: string, + currentCommandAndArgString: string, tokenType: TokenType, shellIntegrationCwd: vscode.Uri | undefined, env: Record, name: string, - precedingText: string, executeExternals: IFigExecuteExternals, token?: vscode.CancellationToken, ): Promise { @@ -46,6 +45,7 @@ export async function getFigSuggestions( hasCurrentArg: false, items: [], }; + const currentCommand = currentCommandAndArgString.split(' ')[0]; for (const spec of specs) { const specLabels = getFigSuggestionLabel(spec); @@ -66,7 +66,7 @@ export async function getFigSuggestions( const description = getFixSuggestionDescription(spec); result.items.push(createCompletionItem( terminalContext.cursorPosition, - prefix, + currentCommandAndArgString, { label: { label: specLabel, description }, kind: vscode.TerminalCompletionItemKind.Method @@ -83,8 +83,8 @@ export async function getFigSuggestions( : availableCommands.filter(command => specLabel === (command.definitionCommand ?? (typeof command.label === 'string' ? command.label : command.label.label)))); if ( !(osIsWindows() - ? commandAndAliases.some(e => precedingText.startsWith(`${removeAnyFileExtension((typeof e.label === 'string' ? e.label : e.label.label))} `)) - : commandAndAliases.some(e => precedingText.startsWith(`${typeof e.label === 'string' ? e.label : e.label.label} `))) + ? commandAndAliases.some(e => currentCommand.startsWith(removeAnyFileExtension((typeof e.label === 'string' ? e.label : e.label.label)))) + : commandAndAliases.some(e => currentCommand.startsWith(typeof e.label === 'string' ? e.label : e.label.label))) ) { continue; } @@ -93,7 +93,7 @@ export async function getFigSuggestions( if (!actualSpec) { continue; } - const completionItemResult = await getFigSpecSuggestions(actualSpec, terminalContext, prefix, shellIntegrationCwd, env, name, executeExternals, token); + const completionItemResult = await getFigSpecSuggestions(actualSpec, terminalContext, currentCommandAndArgString, shellIntegrationCwd, env, name, executeExternals, token); result.hasCurrentArg ||= !!completionItemResult?.hasCurrentArg; if (completionItemResult) { result.filesRequested ||= completionItemResult.filesRequested; diff --git a/extensions/terminal-suggest/src/helpers/completionItem.ts b/extensions/terminal-suggest/src/helpers/completionItem.ts index 236e3e0cc5c..7cb174223c1 100644 --- a/extensions/terminal-suggest/src/helpers/completionItem.ts +++ b/extensions/terminal-suggest/src/helpers/completionItem.ts @@ -6,9 +6,9 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -export function createCompletionItem(cursorPosition: number, prefix: string, commandResource: ICompletionResource, detail?: string, documentation?: string | vscode.MarkdownString, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem { - const endsWithSpace = prefix.endsWith(' '); - const lastWord = endsWithSpace ? '' : prefix.split(' ').at(-1) ?? ''; +export function createCompletionItem(cursorPosition: number, currentCommandString: string, commandResource: ICompletionResource, detail?: string, documentation?: string | vscode.MarkdownString, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem { + const endsWithSpace = currentCommandString.endsWith(' '); + const lastWord = endsWithSpace ? '' : currentCommandString.split(' ').at(-1) ?? ''; return { label: commandResource.label, detail: detail ?? commandResource.detail ?? '', diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 736a064ed21..ab0a8f2cbad 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -20,7 +20,7 @@ import { getBashGlobals } from './shell/bash'; import { getFishGlobals } from './shell/fish'; import { getPwshGlobals } from './shell/pwsh'; import { getZshGlobals } from './shell/zsh'; -import { getTokenType, TokenType } from './tokens'; +import { getTokenType, TokenType, shellTypeResetChars, defaultShellTypeResetChars } from './tokens'; import type { ICompletionResource } from './types'; import { createCompletionItem } from './helpers/completionItem'; import { getFigSuggestions } from './fig/figInterface'; @@ -58,6 +58,7 @@ for (const spec of upstreamSpecs) { const getShellSpecificGlobals: Map) => Promise<(string | ICompletionResource)[]>> = new Map([ [TerminalShellType.Bash, getBashGlobals], [TerminalShellType.Zsh, getZshGlobals], + [TerminalShellType.GitBash, getBashGlobals], // Git Bash is a bash shell // TODO: Ghost text in the command line prevents completions from working ATM for fish [TerminalShellType.Fish, getFishGlobals], [TerminalShellType.PowerShell, getPwshGlobals], @@ -72,7 +73,11 @@ async function getShellGlobals(shellType: TerminalShellType, existingCommands?: if (!shellType) { return; } - const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell: shellType }; + let execShellType = shellType; + if (shellType === TerminalShellType.GitBash) { + execShellType = TerminalShellType.Bash; // Git Bash is a bash shell + } + const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell: execShellType, windowsHide: true }; const mixedCommands: (string | ICompletionResource)[] | undefined = await getShellSpecificGlobals.get(shellType)?.(options, existingCommands); const normalizedCommands = mixedCommands?.map(command => typeof command === 'string' ? ({ label: command }) : command); cachedGlobals.set(shellType, normalizedCommands); @@ -113,7 +118,7 @@ export async function activate(context: vscode.ExtensionContext) { } // Order is important here, add shell globals first so they are prioritized over path commands const commands = [...shellGlobals, ...commandsInPath.completionResources]; - const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); + const currentCommandString = getCurrentCommandAndArgs(terminalContext.commandLine, terminalContext.cursorPosition, terminalShellType); const pathSeparator = isWindows ? '\\' : '/'; const tokenType = getTokenType(terminalContext, terminalShellType); const result = await Promise.race([ @@ -121,7 +126,7 @@ export async function activate(context: vscode.ExtensionContext) { availableSpecs, terminalContext, commands, - prefix, + currentCommandString, tokenType, terminal.shellIntegration?.cwd, getEnvAsRecord(currentTerminalEnv), @@ -143,7 +148,13 @@ export async function activate(context: vscode.ExtensionContext) { } if (terminal.shellIntegration?.cwd && (result.filesRequested || result.foldersRequested)) { - return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, fileExtensions: result.fileExtensions, cwd: result.cwd ?? terminal.shellIntegration.cwd, env: terminal.shellIntegration?.env?.value, }); + return new vscode.TerminalCompletionList(result.items, { + filesRequested: result.filesRequested, + foldersRequested: result.foldersRequested, + fileExtensions: result.fileExtensions, + cwd: result.cwd ?? terminal.shellIntegration.cwd, + env: terminal.shellIntegration?.env?.value, + }); } return result.items; } @@ -152,12 +163,14 @@ export async function activate(context: vscode.ExtensionContext) { } /** - * Adjusts the current working directory based on a given prefix if it is a folder. - * @param prefix - The folder path prefix. + * Adjusts the current working directory based on a given current command string if it is a folder. + * @param currentCommandString - The current command string, which might contain a folder path prefix. * @param currentCwd - The current working directory. * @returns The new working directory. */ -export async function resolveCwdFromPrefix(prefix: string, currentCwd?: vscode.Uri): Promise { +export async function resolveCwdFromCurrentCommandString(currentCommandString: string, currentCwd?: vscode.Uri): Promise { + const prefix = currentCommandString.split(/\s+/).pop()?.trim() ?? ''; + if (!currentCwd) { return; } @@ -195,7 +208,10 @@ export async function resolveCwdFromPrefix(prefix: string, currentCwd?: vscode.U return undefined; } -function getPrefix(commandLine: string, cursorPosition: number): string { +// Retrurns the string that represents the current command and its arguments up to the cursor position. +// Uses shell specific separators to determine the current command and its arguments. +export function getCurrentCommandAndArgs(commandLine: string, cursorPosition: number, shellType: TerminalShellType | undefined): string { + // Return an empty string if the command line is empty after trimming if (commandLine.trim() === '') { return ''; @@ -209,11 +225,21 @@ function getPrefix(commandLine: string, cursorPosition: number): string { // Extract the part of the line up to the cursor position const beforeCursor = commandLine.slice(0, cursorPosition); - // Find the last sequence of non-whitespace characters before the cursor - const match = beforeCursor.match(/(\S+)\s*$/); + const resetChars = shellType ? shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars : defaultShellTypeResetChars; + // Find the last reset character before the cursor + let lastResetIndex = -1; + for (const char of resetChars) { + const idx = beforeCursor.lastIndexOf(char); + if (idx > lastResetIndex) { + lastResetIndex = idx; + } + } - // Return the match if found, otherwise undefined - return match ? match[0] : ''; + // The start of the current command string is after the last reset char (plus one for the char itself) + const currentCommandStart = lastResetIndex + 1; + const currentCommandString = beforeCursor.slice(currentCommandStart).replace(/^\s+/, ''); + + return currentCommandString; } export function asArray(x: T | T[]): T[]; @@ -226,7 +252,7 @@ export async function getCompletionItemsFromSpecs( specs: Fig.Spec[], terminalContext: vscode.TerminalCompletionContext, availableCommands: ICompletionResource[], - prefix: string, + currentCommandString: string, tokenType: TokenType, shellIntegrationCwd: vscode.Uri | undefined, env: Record, @@ -240,17 +266,16 @@ export async function getCompletionItemsFromSpecs( let hasCurrentArg = false; let fileExtensions: string[] | undefined; - let precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); if (isWindows) { - const spaceIndex = precedingText.indexOf(' '); - const commandEndIndex = spaceIndex === -1 ? precedingText.length : spaceIndex; - const lastDotIndex = precedingText.lastIndexOf('.', commandEndIndex); + const spaceIndex = currentCommandString.indexOf(' '); + const commandEndIndex = spaceIndex === -1 ? currentCommandString.length : spaceIndex; + const lastDotIndex = currentCommandString.lastIndexOf('.', commandEndIndex); if (lastDotIndex > 0) { // Don't treat dotfiles as extensions - precedingText = precedingText.substring(0, lastDotIndex) + precedingText.substring(spaceIndex); + currentCommandString = currentCommandString.substring(0, lastDotIndex) + currentCommandString.substring(spaceIndex); } } - const result = await getFigSuggestions(specs, terminalContext, availableCommands, prefix, tokenType, shellIntegrationCwd, env, name, precedingText, executeExternals ?? { executeCommand, executeCommandTimeout }, token); + const result = await getFigSuggestions(specs, terminalContext, availableCommands, currentCommandString, tokenType, shellIntegrationCwd, env, name, executeExternals ?? { executeCommand, executeCommandTimeout }, token); if (result) { hasCurrentArg ||= result.hasCurrentArg; filesRequested ||= result.filesRequested; @@ -266,10 +291,12 @@ export async function getCompletionItemsFromSpecs( const labels = new Set(items.map((i) => typeof i.label === 'string' ? i.label : i.label.label)); for (const command of availableCommands) { const commandTextLabel = typeof command.label === 'string' ? command.label : command.label.label; - if (!labels.has(commandTextLabel)) { + // Remove any file extension for matching on Windows + const labelWithoutExtension = isWindows ? commandTextLabel.replace(/\.[^ ]+$/, '') : commandTextLabel; + if (!labels.has(labelWithoutExtension)) { items.push(createCompletionItem( terminalContext.cursorPosition, - prefix, + currentCommandString, command, command.detail, command.documentation, @@ -300,7 +327,7 @@ export async function getCompletionItemsFromSpecs( let cwd: vscode.Uri | undefined; if (shellIntegrationCwd && (filesRequested || foldersRequested)) { - cwd = await resolveCwdFromPrefix(prefix, shellIntegrationCwd); + cwd = await resolveCwdFromCurrentCommandString(currentCommandString, shellIntegrationCwd); } return { items, filesRequested, foldersRequested, fileExtensions, cwd }; @@ -361,3 +388,4 @@ export function sanitizeProcessEnvironment(env: Record, ...prese } }); } + diff --git a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts index 149f75ad5d4..fa9bd131a4d 100644 --- a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts +++ b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts @@ -5,6 +5,7 @@ import 'mocha'; import { strictEqual } from 'node:assert'; +import type { MarkdownString } from 'vscode'; import { PathExecutableCache } from '../../env/pathExecutableCache'; suite('PathExecutableCache', () => { @@ -31,4 +32,39 @@ suite('PathExecutableCache', () => { const result2 = await cache.getExecutablesInPath(env); strictEqual(result !== result2, true); }); + + if (process.platform !== 'win32') { + test('cache should include executables found via symbolic links', async () => { + const path = require('path'); + // Always use the source fixture directory to ensure symlinks are present + const fixtureDir = path.resolve(__dirname.replace(/out[\/].*$/, 'src/test/env'), '../fixtures/symlink-test'); + const env = { PATH: fixtureDir }; + const cache = new PathExecutableCache(); + const result = await cache.getExecutablesInPath(env); + cache.refresh(); + const labels = Array.from(result!.labels!); + + strictEqual(labels.includes('real-executable.sh'), true); + strictEqual(labels.includes('symlink-executable.sh'), true); + strictEqual(result?.completionResources?.size, 2); + + const completionResources = result!.completionResources!; + let realDocRaw: string | MarkdownString | undefined = undefined; + let symlinkDocRaw: string | MarkdownString | undefined = undefined; + for (const resource of completionResources) { + if (resource.label === 'real-executable.sh') { + realDocRaw = resource.documentation; + } else if (resource.label === 'symlink-executable.sh') { + symlinkDocRaw = resource.documentation; + } + } + const realDoc = typeof realDocRaw === 'string' ? realDocRaw : (realDocRaw && 'value' in realDocRaw ? realDocRaw.value : undefined); + const symlinkDoc = typeof symlinkDocRaw === 'string' ? symlinkDocRaw : (symlinkDocRaw && 'value' in symlinkDocRaw ? symlinkDocRaw.value : undefined); + + const realPath = path.join(fixtureDir, 'real-executable.sh'); + const symlinkPath = path.join(fixtureDir, 'symlink-executable.sh'); + strictEqual(realDoc, realPath); + strictEqual(symlinkDoc, `${symlinkPath} -> ${realPath}`); + }); + } }); diff --git a/extensions/terminal-suggest/src/test/fixtures/symlink-test/real-executable.sh b/extensions/terminal-suggest/src/test/fixtures/symlink-test/real-executable.sh new file mode 100755 index 00000000000..8ff19e15b3c --- /dev/null +++ b/extensions/terminal-suggest/src/test/fixtures/symlink-test/real-executable.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "real executable" diff --git a/extensions/terminal-suggest/src/test/fixtures/symlink-test/symlink-executable.sh b/extensions/terminal-suggest/src/test/fixtures/symlink-test/symlink-executable.sh new file mode 120000 index 00000000000..8f2b5db2692 --- /dev/null +++ b/extensions/terminal-suggest/src/test/fixtures/symlink-test/symlink-executable.sh @@ -0,0 +1 @@ +real-executable.sh \ No newline at end of file diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 85f9cb6ca98..ce4fbb7bba6 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -6,7 +6,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import 'mocha'; import { basename } from 'path'; -import { asArray, getCompletionItemsFromSpecs } from '../terminalSuggestMain'; +import { asArray, getCompletionItemsFromSpecs, getCurrentCommandAndArgs } from '../terminalSuggestMain'; import { getTokenType } from '../tokens'; import { cdTestSuiteSpec as cdTestSuite } from './completions/cd.test'; import { codeSpecOptionsAndSubcommands, codeTestSuite, codeTunnelTestSuite } from './completions/code.test'; @@ -92,7 +92,7 @@ suite('Terminal Suggest', () => { test(`'${testSpec.input}' -> ${expectedString}`, async () => { const commandLine = testSpec.input.split('|')[0]; const cursorPosition = testSpec.input.indexOf('|'); - const prefix = commandLine.slice(0, cursorPosition).split(' ').at(-1) || ''; + const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorPosition, undefined); const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; const terminalContext = { commandLine, cursorPosition, allowFallbackCompletions: true }; @@ -100,7 +100,7 @@ suite('Terminal Suggest', () => { completionSpecs, terminalContext, availableCommands.map(c => { return { label: c }; }), - prefix, + currentCommandString, getTokenType(terminalContext, undefined), testPaths.cwd, {}, diff --git a/extensions/terminal-suggest/src/test/tokens.test.ts b/extensions/terminal-suggest/src/test/tokens.test.ts index 621a9b65388..fb3ea702bab 100644 --- a/extensions/terminal-suggest/src/test/tokens.test.ts +++ b/extensions/terminal-suggest/src/test/tokens.test.ts @@ -48,5 +48,54 @@ suite('Terminal Suggest', () => { test('arguments after reset char', () => { strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorPosition: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); }); + test('; reset char', () => { + strictEqual(getTokenType({ commandLine: `Write-Host hello; `, cursorPosition: `Write-Host hello; `.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + suite('multiple commands on the line', () => { + test('multiple commands, cursor at end', () => { + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, undefined), TokenType.Command); + // Bash + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, TerminalShellType.Bash), TokenType.Command); + // Zsh + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, TerminalShellType.Zsh), TokenType.Command); + // Fish (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; ech'.length }, TerminalShellType.Fish), TokenType.Command); + // PowerShell (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; ech'.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + test('multiple commands, cursor mid text', () => { + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, undefined), TokenType.Argument); + // Bash + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, TerminalShellType.Bash), TokenType.Argument); + // Zsh + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, TerminalShellType.Zsh), TokenType.Argument); + // Fish (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; echo w'.length }, TerminalShellType.Fish), TokenType.Argument); + // PowerShell (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; echo w'.length }, TerminalShellType.PowerShell), TokenType.Argument); + }); + test('multiple commands, cursor at end with reset char', () => { + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, undefined), TokenType.Command); + // Bash + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, TerminalShellType.Bash), TokenType.Command); + // Zsh + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, TerminalShellType.Zsh), TokenType.Command); + // Fish (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo world; '.length }, TerminalShellType.Fish), TokenType.Command); + // PowerShell (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo world; '.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + test('multiple commands, cursor mid text with reset char', () => { + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, undefined), TokenType.Argument); + // Bash + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, TerminalShellType.Bash), TokenType.Argument); + // Zsh + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, TerminalShellType.Zsh), TokenType.Argument); + // Fish (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo worl'.length }, TerminalShellType.Fish), TokenType.Argument); + // PowerShell (use ';' as separator) + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo worl'.length }, TerminalShellType.PowerShell), TokenType.Argument); + }); + }); }); }); diff --git a/extensions/terminal-suggest/src/tokens.ts b/extensions/terminal-suggest/src/tokens.ts index 0520a2315a4..ee39314b6f9 100644 --- a/extensions/terminal-suggest/src/tokens.ts +++ b/extensions/terminal-suggest/src/tokens.ts @@ -11,21 +11,38 @@ export const enum TokenType { Argument, } -const shellTypeResetChars = new Map([ +export const shellTypeResetChars = new Map([ [TerminalShellType.Bash, ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<']], [TerminalShellType.Zsh, ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '<>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<', '<<<', '<(']], - [TerminalShellType.PowerShell, ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin']] + [TerminalShellType.PowerShell, ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', ';', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin']] ]); -const defaultShellTypeResetChars = shellTypeResetChars.get(TerminalShellType.Bash)!; +export const defaultShellTypeResetChars = shellTypeResetChars.get(TerminalShellType.Bash)!; export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType { - const spaceIndex = ctx.commandLine.substring(0, ctx.cursorPosition).lastIndexOf(' '); + const commandLine = ctx.commandLine; + const cursorPosition = ctx.cursorPosition; + const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars; + + // Check for reset char before the current word + const beforeCursor = commandLine.substring(0, cursorPosition); + const wordStart = beforeCursor.lastIndexOf(' ') + 1; + const beforeWord = commandLine.substring(0, wordStart); + + // Look for " " before the word + for (const resetChar of commandResetChars) { + const pattern = ` ${resetChar} `; + if (beforeWord.endsWith(pattern)) { + return TokenType.Command; + } + } + + // Fallback to original logic for the very first command + const spaceIndex = beforeCursor.lastIndexOf(' '); if (spaceIndex === -1) { return TokenType.Command; } - const previousTokens = ctx.commandLine.substring(0, spaceIndex + 1).trim(); - const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars; + const previousTokens = beforeCursor.substring(0, spaceIndex + 1).trim(); if (commandResetChars.some(e => previousTokens.endsWith(e))) { return TokenType.Command; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index 106ef702d69..e2105f2e017 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -295,4 +295,19 @@ suite('vscode API - editors', () => { assert.strictEqual(document.getText(), Buffer.from(await workspace.fs.readFile(file)).toString()); } + + test('extEditor.selection can be empty #18075', async function () { + await withRandomFileEditor('foo', async editor => { + + assert.ok(editor.selections.length > 0); + + editor.selections = []; + + assert.strictEqual(editor.selections.length, 1); + assert.strictEqual(editor.selections[0].start.line, 0); + assert.strictEqual(editor.selections[0].start.character, 0); + assert.strictEqual(editor.selections[0].end.line, 0); + assert.strictEqual(editor.selections[0].end.character, 0); + }); + }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 7ee205256bb..549bd426c13 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -1399,6 +1399,14 @@ suite('vscode API - workspace', () => { assert.strictEqual(doc2.encoding, 'cp1252'); }); + test('encoding: openTextDocument - can change the encoding of an existing untitled document', async () => { + const doc = await vscode.workspace.openTextDocument({ content: 'Hello World' }); + assert.strictEqual(doc.encoding, 'utf8'); + + await vscode.workspace.openTextDocument(doc.uri, { encoding: 'windows1252' }); + assert.strictEqual(doc.encoding, 'windows1252'); + }); + test('encoding: decode', async function () { const uri = root.with({ path: posix.join(root.path, 'file.txt') }); diff --git a/package-lock.json b/package-lock.json index 95b349d56e0..f315dcef4fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-oss-dev", - "version": "1.101.0", + "version": "1.102.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-oss-dev", - "version": "1.101.0", + "version": "1.102.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -18,7 +18,7 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.3.2", "@vscode/proxy-agent": "^0.32.0", - "@vscode/ripgrep": "^1.15.11", + "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", @@ -27,26 +27,26 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.90", - "@xterm/addon-image": "^0.9.0-beta.107", - "@xterm/addon-ligatures": "^0.10.0-beta.107", - "@xterm/addon-progress": "^0.2.0-beta.13", - "@xterm/addon-search": "^0.16.0-beta.107", - "@xterm/addon-serialize": "^0.14.0-beta.107", - "@xterm/addon-unicode11": "^0.9.0-beta.107", - "@xterm/addon-webgl": "^0.19.0-beta.107", - "@xterm/headless": "^5.6.0-beta.107", - "@xterm/xterm": "^5.6.0-beta.107", + "@xterm/addon-clipboard": "^0.2.0-beta.93", + "@xterm/addon-image": "^0.9.0-beta.110", + "@xterm/addon-ligatures": "^0.10.0-beta.110", + "@xterm/addon-progress": "^0.2.0-beta.16", + "@xterm/addon-search": "^0.16.0-beta.110", + "@xterm/addon-serialize": "^0.14.0-beta.110", + "@xterm/addon-unicode11": "^0.9.0-beta.110", + "@xterm/addon-webgl": "^0.19.0-beta.110", + "@xterm/headless": "^5.6.0-beta.110", + "@xterm/xterm": "^5.6.0-beta.110", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", "kerberos": "2.1.1", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "native-is-elevated": "0.7.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta33", - "open": "^8.4.2", + "open": "^10.1.2", "tas-client-umd": "0.2.0", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", @@ -131,7 +131,6 @@ "merge-options": "^1.0.1", "mime": "^1.4.1", "minimatch": "^3.0.4", - "minimist": "^1.2.6", "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", @@ -151,7 +150,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.9.0-dev.20250522", + "typescript": "^5.9.0-dev.20250613", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -2713,9 +2712,9 @@ } }, "node_modules/@vscode/ripgrep": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.11.tgz", - "integrity": "sha512-G/VqtA6kR50mJkIH4WA+I2Q78V5blovgPPq0VPYM0QIRp57lYMkdV+U9VrY80b3AvaC72A1z8STmyxc8ZKiTsw==", + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.13.tgz", + "integrity": "sha512-c62On5sLEIMv9yfetIZ8c0Mg0s6lvq54G0YLW2zY/XiPsJPyBBgrP54MR1s0hK3gsPy423cY+e2BDU6mOB2Yaw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3287,30 +3286,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.90", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.90.tgz", - "integrity": "sha512-2i7qtACRBYRTRba831ufEjQMeAvK4uuVPYPBkSXzKJ/dIAvhws5B6OOmxqZzR97OsmzUC/SrjHSujgwyD0MVVw==", + "version": "0.2.0-beta.93", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.93.tgz", + "integrity": "sha512-A+XZnJgROYtvcIVGGYM6lH5PbXdVoMz/c140Pq9wu0jq++z3zHkcfTIMWO8E/xrvCO9Op3LTv9fu5NEhOI2l3A==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.107.tgz", - "integrity": "sha512-/vqb2BhVjJeCcOIDSpjWrfXbhNTCse95qkKbpmzFkQJJEh0CyaqjSUBIAM+Qcl9f/x6H//O2wADnn3Wsn5qYsg==", + "version": "0.9.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.110.tgz", + "integrity": "sha512-mzs2nIWqVay/1q3H74UQZwE0/MU4pNTEIkle3n+sYYbFJh8ZVOPKQfnl5ze7DWnQnqm3hqg2FDXZKfXj8fkdEA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.107.tgz", - "integrity": "sha512-3qQZz6dPS8XlGGwz1p5Bqjoosah6Km1GwGeNv7YxkCVU2kpTOymFH8BMUEOFC5WHdYseDXnz8hIEleexF9Pa2g==", + "version": "0.10.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.110.tgz", + "integrity": "sha512-k8ILGtdbmUTJURGX0h857C3F6KXxIwrOpsfEC5v1k3ffW3mJn6qkKOTmOyS89QcpWXXbbqvLJRLQ7FWGKYvlgg==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -3320,64 +3319,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.13", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.13.tgz", - "integrity": "sha512-BLmX+JdA+LLOMbVLQm+gPqieeKaVMiugBFpCtQtBus3kCs/2/F8pU/XNGV31CnXxDdRjptyOGrPVRreF/rDdcg==", + "version": "0.2.0-beta.16", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.16.tgz", + "integrity": "sha512-VqxaYIMx8TjeL9s4CYxMP8kFOQPEhcjEzrSLqYmpvhY2RpCJrDjf0HPh/wy9HDADJIWLZE6Gjcva7yub02fJXg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.107.tgz", - "integrity": "sha512-poHjeKjnTKtS6rHyu3r5lhW/0rdLcRMSJLDmT9xo9lfTuyhfcUl8gro44iPtscKY+LEtrhZcortTlElMewy/Bw==", + "version": "0.16.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.110.tgz", + "integrity": "sha512-f7pi12E/7lD5Oz/Wrt3DL6kXOU7jVjA6ZAXisznBQTH1M4vxY3agvgUYVHWYFHyY1uWwheL1K089nzfjT5A1Lg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.107.tgz", - "integrity": "sha512-MGaoO2zlNuSGofX5Xfbw+MU2wgAApEsDOPeYoEBSQyLf5BR7Z2rGVCxFyF5zNsIhNDov7ptQIVpCfonMVAgUvg==", + "version": "0.14.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.110.tgz", + "integrity": "sha512-uCHdHwwp2HWTCSMavkmYylgebiCGW+P76XJp04uVJnMBIPTSLGKygiCRQbxDsRB6mblsYislMEw4P9egKl/eqA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.107.tgz", - "integrity": "sha512-XTfJ1G7u+8zOT6faCfEB49AbVFAX4XWUoiLtyXCfRvbrVuv5wv53T03dLtBtpwjWoFaveLKGl3ZiU7t3dpl0RA==", + "version": "0.9.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.110.tgz", + "integrity": "sha512-HMfvm7ZaUgN2I78Lk/YXazfnLTntCwZidi25BtVNJWy+ibgSM9EUfs/AsnDx8JbeTPqGpHxvybWaIUC3maNurQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.107.tgz", - "integrity": "sha512-wdUkYiV4PnR9+7+1giJoFm5xrguCOKTH4xdQ/6lYuSbgMS0y1ykpcQjDNshwHy0TyUuh4o4z1+rd48qNjqGM6Q==", + "version": "0.19.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.110.tgz", + "integrity": "sha512-1fVHcG5lKdencCBI6b6ZZwlTs54Ax3EtbngkgwVHnqVR4jMmHQ5E8G/AH6Vk+OpVNANW+//oArDtosFUIDFKWw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.107.tgz", - "integrity": "sha512-jFoJEPXjffcSzQyf/fTh3eZsXuHP/xQmkezkKbmrkzxw5h40NPbTPQbhTrsUCea2M12Mun1BrT8ScSYMjUCH3w==", + "version": "5.6.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.110.tgz", + "integrity": "sha512-3oT+fvaQ8kr6kBIuBhH28Npg0KYSCcR6Jz/nJ1f23N0ANBqDJV2Ou0VDqfX+KsxdReG5moYKcYzhlccpdoy34A==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.107.tgz", - "integrity": "sha512-7cuJFZtc7Rv9BEpf9UsvErfXhLKkEmogI0mizgri+nNjEENnpQcFOpx84GFTjax/Zta8MAxOMUP1XU5Guju80A==", + "version": "5.6.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.110.tgz", + "integrity": "sha512-FPIKLySxbyWaXoBVbnc1KtbJe6CUV9DxZ9LHSs2gNHMN2jPmBhtdpDppeMqWwdmuCDBk9OcMSTPQNHT+mI4gSw==", "license": "MIT" }, "node_modules/@xtuc/ieee754": { @@ -4324,6 +4323,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5513,6 +5527,34 @@ "node": ">=4.0.0" } }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -5561,11 +5603,15 @@ } }, "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/define-properties": { @@ -10144,14 +10190,15 @@ } }, "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10230,6 +10277,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -10419,14 +10484,18 @@ } }, "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { @@ -12877,16 +12946,18 @@ "dev": true }, "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "license": "MIT", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14624,6 +14695,18 @@ "node": ">=8.0" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", @@ -16701,9 +16784,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.9.0-dev.20250522", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.0-dev.20250522.tgz", - "integrity": "sha512-5zi45joB4lxgbZokE23gOC139Blh5v/yOBqjO+mXURkJiDsEkfV/JJmTKyMpI8W7wnIx6H7Nyd9zxYOntclNZw==", + "version": "5.9.0-dev.20250613", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.0-dev.20250613.tgz", + "integrity": "sha512-5bzU//x0svUVjUCMlHRG7IassWFQ7/dofYXqSSbQpQ8a1Kh/GqIsvfc2CQIS+EpYrdoHaNLroxmlsLNsqLXaww==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index f7c34e248f0..c1e62db2fb1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.101.0", - "distro": "0110618df4664082ca0cb597c5f9bf66aeed2e35", + "version": "1.102.0", + "distro": "90dfd14573cba753b9d5d02aa01f48dc1e8c4db9", "author": { "name": "Microsoft Corporation" }, @@ -44,7 +44,7 @@ "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", "tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json", "vscode-dts-compile-check": "tsc -p src/tsconfig.vscode-dts.json && tsc -p src/tsconfig.vscode-proposed-dts.json", - "valid-layers-check": "node build/lib/layersChecker.js", + "valid-layers-check": "node build/checker/layersChecker.js && tsc -p build/checker/tsconfig.browser.json && tsc -p build/checker/tsconfig.worker.json && tsc -p build/checker/tsconfig.node.json && tsc -p build/checker/tsconfig.electron-browser.json && tsc -p build/checker/tsconfig.electron-main.json && tsc -p build/checker/tsconfig.electron-utility.json", "define-class-fields-check": "node build/lib/propertyInitOrderChecker.js && tsc -p src/tsconfig.defineClassFields.json", "update-distro": "node build/npm/update-distro.mjs", "web": "echo 'npm run web' is replaced by './scripts/code-server' or './scripts/code-web'", @@ -77,7 +77,7 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.3.2", "@vscode/proxy-agent": "^0.32.0", - "@vscode/ripgrep": "^1.15.11", + "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", @@ -86,26 +86,26 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.90", - "@xterm/addon-image": "^0.9.0-beta.107", - "@xterm/addon-ligatures": "^0.10.0-beta.107", - "@xterm/addon-progress": "^0.2.0-beta.13", - "@xterm/addon-search": "^0.16.0-beta.107", - "@xterm/addon-serialize": "^0.14.0-beta.107", - "@xterm/addon-unicode11": "^0.9.0-beta.107", - "@xterm/addon-webgl": "^0.19.0-beta.107", - "@xterm/headless": "^5.6.0-beta.107", - "@xterm/xterm": "^5.6.0-beta.107", + "@xterm/addon-clipboard": "^0.2.0-beta.93", + "@xterm/addon-image": "^0.9.0-beta.110", + "@xterm/addon-ligatures": "^0.10.0-beta.110", + "@xterm/addon-progress": "^0.2.0-beta.16", + "@xterm/addon-search": "^0.16.0-beta.110", + "@xterm/addon-serialize": "^0.14.0-beta.110", + "@xterm/addon-unicode11": "^0.9.0-beta.110", + "@xterm/addon-webgl": "^0.19.0-beta.110", + "@xterm/headless": "^5.6.0-beta.110", + "@xterm/xterm": "^5.6.0-beta.110", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", "kerberos": "2.1.1", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "native-is-elevated": "0.7.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta33", - "open": "^8.4.2", + "open": "^10.1.2", "tas-client-umd": "0.2.0", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", @@ -190,7 +190,6 @@ "merge-options": "^1.0.1", "mime": "^1.4.1", "minimatch": "^3.0.4", - "minimist": "^1.2.6", "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", @@ -210,7 +209,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.9.0-dev.20250522", + "typescript": "^5.9.0-dev.20250613", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", diff --git a/remote/package-lock.json b/remote/package-lock.json index 55f439fb658..9efb43a4180 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -14,28 +14,28 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.32.0", - "@vscode/ripgrep": "^1.15.11", + "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.90", - "@xterm/addon-image": "^0.9.0-beta.107", - "@xterm/addon-ligatures": "^0.10.0-beta.107", - "@xterm/addon-progress": "^0.2.0-beta.13", - "@xterm/addon-search": "^0.16.0-beta.107", - "@xterm/addon-serialize": "^0.14.0-beta.107", - "@xterm/addon-unicode11": "^0.9.0-beta.107", - "@xterm/addon-webgl": "^0.19.0-beta.107", - "@xterm/headless": "^5.6.0-beta.107", - "@xterm/xterm": "^5.6.0-beta.107", + "@xterm/addon-clipboard": "^0.2.0-beta.93", + "@xterm/addon-image": "^0.9.0-beta.110", + "@xterm/addon-ligatures": "^0.10.0-beta.110", + "@xterm/addon-progress": "^0.2.0-beta.16", + "@xterm/addon-search": "^0.16.0-beta.110", + "@xterm/addon-serialize": "^0.14.0-beta.110", + "@xterm/addon-unicode11": "^0.9.0-beta.110", + "@xterm/addon-webgl": "^0.19.0-beta.110", + "@xterm/headless": "^5.6.0-beta.110", + "@xterm/xterm": "^5.6.0-beta.110", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", "kerberos": "2.1.1", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta33", "tas-client-umd": "0.2.0", @@ -437,9 +437,9 @@ } }, "node_modules/@vscode/ripgrep": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.11.tgz", - "integrity": "sha512-G/VqtA6kR50mJkIH4WA+I2Q78V5blovgPPq0VPYM0QIRp57lYMkdV+U9VrY80b3AvaC72A1z8STmyxc8ZKiTsw==", + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.13.tgz", + "integrity": "sha512-c62On5sLEIMv9yfetIZ8c0Mg0s6lvq54G0YLW2zY/XiPsJPyBBgrP54MR1s0hK3gsPy423cY+e2BDU6mOB2Yaw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -523,30 +523,30 @@ "hasInstallScript": true }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.90", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.90.tgz", - "integrity": "sha512-2i7qtACRBYRTRba831ufEjQMeAvK4uuVPYPBkSXzKJ/dIAvhws5B6OOmxqZzR97OsmzUC/SrjHSujgwyD0MVVw==", + "version": "0.2.0-beta.93", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.93.tgz", + "integrity": "sha512-A+XZnJgROYtvcIVGGYM6lH5PbXdVoMz/c140Pq9wu0jq++z3zHkcfTIMWO8E/xrvCO9Op3LTv9fu5NEhOI2l3A==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.107.tgz", - "integrity": "sha512-/vqb2BhVjJeCcOIDSpjWrfXbhNTCse95qkKbpmzFkQJJEh0CyaqjSUBIAM+Qcl9f/x6H//O2wADnn3Wsn5qYsg==", + "version": "0.9.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.110.tgz", + "integrity": "sha512-mzs2nIWqVay/1q3H74UQZwE0/MU4pNTEIkle3n+sYYbFJh8ZVOPKQfnl5ze7DWnQnqm3hqg2FDXZKfXj8fkdEA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.107.tgz", - "integrity": "sha512-3qQZz6dPS8XlGGwz1p5Bqjoosah6Km1GwGeNv7YxkCVU2kpTOymFH8BMUEOFC5WHdYseDXnz8hIEleexF9Pa2g==", + "version": "0.10.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.110.tgz", + "integrity": "sha512-k8ILGtdbmUTJURGX0h857C3F6KXxIwrOpsfEC5v1k3ffW3mJn6qkKOTmOyS89QcpWXXbbqvLJRLQ7FWGKYvlgg==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -556,64 +556,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.13", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.13.tgz", - "integrity": "sha512-BLmX+JdA+LLOMbVLQm+gPqieeKaVMiugBFpCtQtBus3kCs/2/F8pU/XNGV31CnXxDdRjptyOGrPVRreF/rDdcg==", + "version": "0.2.0-beta.16", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.16.tgz", + "integrity": "sha512-VqxaYIMx8TjeL9s4CYxMP8kFOQPEhcjEzrSLqYmpvhY2RpCJrDjf0HPh/wy9HDADJIWLZE6Gjcva7yub02fJXg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.107.tgz", - "integrity": "sha512-poHjeKjnTKtS6rHyu3r5lhW/0rdLcRMSJLDmT9xo9lfTuyhfcUl8gro44iPtscKY+LEtrhZcortTlElMewy/Bw==", + "version": "0.16.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.110.tgz", + "integrity": "sha512-f7pi12E/7lD5Oz/Wrt3DL6kXOU7jVjA6ZAXisznBQTH1M4vxY3agvgUYVHWYFHyY1uWwheL1K089nzfjT5A1Lg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.107.tgz", - "integrity": "sha512-MGaoO2zlNuSGofX5Xfbw+MU2wgAApEsDOPeYoEBSQyLf5BR7Z2rGVCxFyF5zNsIhNDov7ptQIVpCfonMVAgUvg==", + "version": "0.14.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.110.tgz", + "integrity": "sha512-uCHdHwwp2HWTCSMavkmYylgebiCGW+P76XJp04uVJnMBIPTSLGKygiCRQbxDsRB6mblsYislMEw4P9egKl/eqA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.107.tgz", - "integrity": "sha512-XTfJ1G7u+8zOT6faCfEB49AbVFAX4XWUoiLtyXCfRvbrVuv5wv53T03dLtBtpwjWoFaveLKGl3ZiU7t3dpl0RA==", + "version": "0.9.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.110.tgz", + "integrity": "sha512-HMfvm7ZaUgN2I78Lk/YXazfnLTntCwZidi25BtVNJWy+ibgSM9EUfs/AsnDx8JbeTPqGpHxvybWaIUC3maNurQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.107.tgz", - "integrity": "sha512-wdUkYiV4PnR9+7+1giJoFm5xrguCOKTH4xdQ/6lYuSbgMS0y1ykpcQjDNshwHy0TyUuh4o4z1+rd48qNjqGM6Q==", + "version": "0.19.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.110.tgz", + "integrity": "sha512-1fVHcG5lKdencCBI6b6ZZwlTs54Ax3EtbngkgwVHnqVR4jMmHQ5E8G/AH6Vk+OpVNANW+//oArDtosFUIDFKWw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.107.tgz", - "integrity": "sha512-jFoJEPXjffcSzQyf/fTh3eZsXuHP/xQmkezkKbmrkzxw5h40NPbTPQbhTrsUCea2M12Mun1BrT8ScSYMjUCH3w==", + "version": "5.6.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.110.tgz", + "integrity": "sha512-3oT+fvaQ8kr6kBIuBhH28Npg0KYSCcR6Jz/nJ1f23N0ANBqDJV2Ou0VDqfX+KsxdReG5moYKcYzhlccpdoy34A==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.107.tgz", - "integrity": "sha512-7cuJFZtc7Rv9BEpf9UsvErfXhLKkEmogI0mizgri+nNjEENnpQcFOpx84GFTjax/Zta8MAxOMUP1XU5Guju80A==", + "version": "5.6.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.110.tgz", + "integrity": "sha512-FPIKLySxbyWaXoBVbnc1KtbJe6CUV9DxZ9LHSs2gNHMN2jPmBhtdpDppeMqWwdmuCDBk9OcMSTPQNHT+mI4gSw==", "license": "MIT" }, "node_modules/agent-base": { @@ -1041,9 +1041,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp": { "version": "1.0.4", diff --git a/remote/package.json b/remote/package.json index 758dbd9201c..6a6fe3dfff4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -9,28 +9,28 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.32.0", - "@vscode/ripgrep": "^1.15.11", + "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.90", - "@xterm/addon-image": "^0.9.0-beta.107", - "@xterm/addon-ligatures": "^0.10.0-beta.107", - "@xterm/addon-progress": "^0.2.0-beta.13", - "@xterm/addon-search": "^0.16.0-beta.107", - "@xterm/addon-serialize": "^0.14.0-beta.107", - "@xterm/addon-unicode11": "^0.9.0-beta.107", - "@xterm/addon-webgl": "^0.19.0-beta.107", - "@xterm/headless": "^5.6.0-beta.107", - "@xterm/xterm": "^5.6.0-beta.107", + "@xterm/addon-clipboard": "^0.2.0-beta.93", + "@xterm/addon-image": "^0.9.0-beta.110", + "@xterm/addon-ligatures": "^0.10.0-beta.110", + "@xterm/addon-progress": "^0.2.0-beta.16", + "@xterm/addon-search": "^0.16.0-beta.110", + "@xterm/addon-serialize": "^0.14.0-beta.110", + "@xterm/addon-unicode11": "^0.9.0-beta.110", + "@xterm/addon-webgl": "^0.19.0-beta.110", + "@xterm/headless": "^5.6.0-beta.110", + "@xterm/xterm": "^5.6.0-beta.110", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", "kerberos": "2.1.1", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta33", "tas-client-umd": "0.2.0", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index 68dc9987bcf..ae065ffef5a 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -13,15 +13,15 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.90", - "@xterm/addon-image": "^0.9.0-beta.107", - "@xterm/addon-ligatures": "^0.10.0-beta.107", - "@xterm/addon-progress": "^0.2.0-beta.13", - "@xterm/addon-search": "^0.16.0-beta.107", - "@xterm/addon-serialize": "^0.14.0-beta.107", - "@xterm/addon-unicode11": "^0.9.0-beta.107", - "@xterm/addon-webgl": "^0.19.0-beta.107", - "@xterm/xterm": "^5.6.0-beta.107", + "@xterm/addon-clipboard": "^0.2.0-beta.93", + "@xterm/addon-image": "^0.9.0-beta.110", + "@xterm/addon-ligatures": "^0.10.0-beta.110", + "@xterm/addon-progress": "^0.2.0-beta.16", + "@xterm/addon-search": "^0.16.0-beta.110", + "@xterm/addon-serialize": "^0.14.0-beta.110", + "@xterm/addon-unicode11": "^0.9.0-beta.110", + "@xterm/addon-webgl": "^0.19.0-beta.110", + "@xterm/xterm": "^5.6.0-beta.110", "jschardet": "3.1.4", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", @@ -90,30 +90,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.90", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.90.tgz", - "integrity": "sha512-2i7qtACRBYRTRba831ufEjQMeAvK4uuVPYPBkSXzKJ/dIAvhws5B6OOmxqZzR97OsmzUC/SrjHSujgwyD0MVVw==", + "version": "0.2.0-beta.93", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.93.tgz", + "integrity": "sha512-A+XZnJgROYtvcIVGGYM6lH5PbXdVoMz/c140Pq9wu0jq++z3zHkcfTIMWO8E/xrvCO9Op3LTv9fu5NEhOI2l3A==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.107.tgz", - "integrity": "sha512-/vqb2BhVjJeCcOIDSpjWrfXbhNTCse95qkKbpmzFkQJJEh0CyaqjSUBIAM+Qcl9f/x6H//O2wADnn3Wsn5qYsg==", + "version": "0.9.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.110.tgz", + "integrity": "sha512-mzs2nIWqVay/1q3H74UQZwE0/MU4pNTEIkle3n+sYYbFJh8ZVOPKQfnl5ze7DWnQnqm3hqg2FDXZKfXj8fkdEA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.107.tgz", - "integrity": "sha512-3qQZz6dPS8XlGGwz1p5Bqjoosah6Km1GwGeNv7YxkCVU2kpTOymFH8BMUEOFC5WHdYseDXnz8hIEleexF9Pa2g==", + "version": "0.10.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.110.tgz", + "integrity": "sha512-k8ILGtdbmUTJURGX0h857C3F6KXxIwrOpsfEC5v1k3ffW3mJn6qkKOTmOyS89QcpWXXbbqvLJRLQ7FWGKYvlgg==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -123,58 +123,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.13", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.13.tgz", - "integrity": "sha512-BLmX+JdA+LLOMbVLQm+gPqieeKaVMiugBFpCtQtBus3kCs/2/F8pU/XNGV31CnXxDdRjptyOGrPVRreF/rDdcg==", + "version": "0.2.0-beta.16", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.16.tgz", + "integrity": "sha512-VqxaYIMx8TjeL9s4CYxMP8kFOQPEhcjEzrSLqYmpvhY2RpCJrDjf0HPh/wy9HDADJIWLZE6Gjcva7yub02fJXg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.107.tgz", - "integrity": "sha512-poHjeKjnTKtS6rHyu3r5lhW/0rdLcRMSJLDmT9xo9lfTuyhfcUl8gro44iPtscKY+LEtrhZcortTlElMewy/Bw==", + "version": "0.16.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.110.tgz", + "integrity": "sha512-f7pi12E/7lD5Oz/Wrt3DL6kXOU7jVjA6ZAXisznBQTH1M4vxY3agvgUYVHWYFHyY1uWwheL1K089nzfjT5A1Lg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.107.tgz", - "integrity": "sha512-MGaoO2zlNuSGofX5Xfbw+MU2wgAApEsDOPeYoEBSQyLf5BR7Z2rGVCxFyF5zNsIhNDov7ptQIVpCfonMVAgUvg==", + "version": "0.14.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.110.tgz", + "integrity": "sha512-uCHdHwwp2HWTCSMavkmYylgebiCGW+P76XJp04uVJnMBIPTSLGKygiCRQbxDsRB6mblsYislMEw4P9egKl/eqA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.107.tgz", - "integrity": "sha512-XTfJ1G7u+8zOT6faCfEB49AbVFAX4XWUoiLtyXCfRvbrVuv5wv53T03dLtBtpwjWoFaveLKGl3ZiU7t3dpl0RA==", + "version": "0.9.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.110.tgz", + "integrity": "sha512-HMfvm7ZaUgN2I78Lk/YXazfnLTntCwZidi25BtVNJWy+ibgSM9EUfs/AsnDx8JbeTPqGpHxvybWaIUC3maNurQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.107.tgz", - "integrity": "sha512-wdUkYiV4PnR9+7+1giJoFm5xrguCOKTH4xdQ/6lYuSbgMS0y1ykpcQjDNshwHy0TyUuh4o4z1+rd48qNjqGM6Q==", + "version": "0.19.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.110.tgz", + "integrity": "sha512-1fVHcG5lKdencCBI6b6ZZwlTs54Ax3EtbngkgwVHnqVR4jMmHQ5E8G/AH6Vk+OpVNANW+//oArDtosFUIDFKWw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.107" + "@xterm/xterm": "^5.6.0-beta.110" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.107", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.107.tgz", - "integrity": "sha512-7cuJFZtc7Rv9BEpf9UsvErfXhLKkEmogI0mizgri+nNjEENnpQcFOpx84GFTjax/Zta8MAxOMUP1XU5Guju80A==", + "version": "5.6.0-beta.110", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.110.tgz", + "integrity": "sha512-FPIKLySxbyWaXoBVbnc1KtbJe6CUV9DxZ9LHSs2gNHMN2jPmBhtdpDppeMqWwdmuCDBk9OcMSTPQNHT+mI4gSw==", "license": "MIT" }, "node_modules/font-finder": { diff --git a/remote/web/package.json b/remote/web/package.json index 1f3b42ac717..b1430851c3d 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -8,15 +8,15 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.90", - "@xterm/addon-image": "^0.9.0-beta.107", - "@xterm/addon-ligatures": "^0.10.0-beta.107", - "@xterm/addon-progress": "^0.2.0-beta.13", - "@xterm/addon-search": "^0.16.0-beta.107", - "@xterm/addon-serialize": "^0.14.0-beta.107", - "@xterm/addon-unicode11": "^0.9.0-beta.107", - "@xterm/addon-webgl": "^0.19.0-beta.107", - "@xterm/xterm": "^5.6.0-beta.107", + "@xterm/addon-clipboard": "^0.2.0-beta.93", + "@xterm/addon-image": "^0.9.0-beta.110", + "@xterm/addon-ligatures": "^0.10.0-beta.110", + "@xterm/addon-progress": "^0.2.0-beta.16", + "@xterm/addon-search": "^0.16.0-beta.110", + "@xterm/addon-serialize": "^0.14.0-beta.110", + "@xterm/addon-unicode11": "^0.9.0-beta.110", + "@xterm/addon-webgl": "^0.19.0-beta.110", + "@xterm/xterm": "^5.6.0-beta.110", "jschardet": "3.1.4", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", diff --git a/scripts/code-server.js b/scripts/code-server.js index 245694c8b6d..5226015b23b 100644 --- a/scripts/code-server.js +++ b/scripts/code-server.js @@ -33,7 +33,7 @@ async function main() { const serverArgs = process.argv.slice(2).filter(v => v !== '--launch'); const addr = await startServer(serverArgs); if (args['launch']) { - open(addr); + open.default(addr); } } diff --git a/scripts/code-web.js b/scripts/code-web.js index a66dd17018f..5dc638c0c60 100644 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -80,7 +80,7 @@ async function main() { startServer(serverArgs); if (openSystemBrowser) { - open(`http://${HOST}:${PORT}/`); + open.default(`http://${HOST}:${PORT}/`); } } diff --git a/src/bootstrap-import.ts b/src/bootstrap-import.ts index 8869058159d..9d222a003eb 100644 --- a/src/bootstrap-import.ts +++ b/src/bootstrap-import.ts @@ -6,7 +6,7 @@ // ********************************************************************* // * * // * We need this to redirect to node_modules from the remote-folder. * -// * This ONLY applies when running out of source. * +// * This ONLY applies when running out of source. * // * * // ********************************************************************* diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index 5bb86c7a91f..f913df5e7da 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -1,6 +1,6 @@ { "ban-document-execcommand": [ - "vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts", + "vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts", "vs/editor/contrib/clipboard/browser/clipboard.ts" ], "ban-eval-calls": [ @@ -12,7 +12,7 @@ "vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts" ], "ban-trustedtypes-createpolicy": [ - "vs/code/electron-sandbox/workbench/workbench.ts", + "vs/code/electron-browser/workbench/workbench.ts", "vs/amdX.ts", "vs/base/browser/trustedTypes.ts", "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts" @@ -37,6 +37,6 @@ "**/*.ts" ], "ban-script-content-assignments": [ - "vs/code/electron-sandbox/workbench/workbench.ts" + "vs/code/electron-browser/workbench/workbench.ts" ] } diff --git a/src/typings/base-common.d.ts b/src/typings/base-common.d.ts index 189c1149567..9f0035b43b9 100644 --- a/src/typings/base-common.d.ts +++ b/src/typings/base-common.d.ts @@ -17,6 +17,7 @@ declare global { function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number; function cancelIdleCallback(handle: number): void; + // --- timeout / interval (available in all contexts, but different signatures in node.js vs web) interface TimeoutHandle {readonly _: never; /* this is a trick that seems needed to prevent direct number assignment */} @@ -26,6 +27,14 @@ declare global { function setInterval(callback: (...args: any[]) => void, delay?: number, ...args: any[]): Timeout; function clearInterval(timeout: Timeout | undefined): void; + + + // --- error + + interface ErrorConstructor { + captureStackTrace(targetObject: object, constructorOpt?: Function): void; + stackTraceLimit: number; + } } export { } diff --git a/src/vs/amdX.ts b/src/vs/amdX.ts index 9fd519fd130..b2f9fd3fa91 100644 --- a/src/vs/amdX.ts +++ b/src/vs/amdX.ts @@ -11,6 +11,11 @@ import { generateUuid } from './base/common/uuid.js'; export const canASAR = false; // TODO@esm: ASAR disabled in ESM +declare const window: any; +declare const document: any; +declare const self: any; +declare const globalThis: any; + class DefineCall { constructor( public readonly id: string | null | undefined, @@ -41,7 +46,7 @@ class AMDModuleImporter { private _initialize(): void { if (this._state === AMDModuleImporterState.Uninitialized) { - if ((globalThis as any).define) { + if (globalThis.define) { this._state = AMDModuleImporterState.InitializedExternal; return; } @@ -51,7 +56,7 @@ class AMDModuleImporter { this._state = AMDModuleImporterState.InitializedInternal; - (globalThis as any).define = (id: any, dependencies: any, callback: any) => { + globalThis.define = (id: any, dependencies: any, callback: any) => { if (typeof id !== 'string') { callback = dependencies; dependencies = id; @@ -67,11 +72,11 @@ class AMDModuleImporter { this._defineCalls.push(new DefineCall(id, dependencies, callback)); }; - (globalThis as any).define.amd = true; + globalThis.define.amd = true; if (this._isRenderer) { - this._amdPolicy = (globalThis as any)._VSCODE_WEB_PACKAGE_TTP ?? window.trustedTypes?.createPolicy('amdLoader', { - createScriptURL(value) { + this._amdPolicy = globalThis._VSCODE_WEB_PACKAGE_TTP ?? window.trustedTypes?.createPolicy('amdLoader', { + createScriptURL(value: any) { if (value.startsWith(window.location.origin)) { return value; } @@ -82,7 +87,7 @@ class AMDModuleImporter { } }); } else if (this._isWebWorker) { - this._amdPolicy = (globalThis as any)._VSCODE_WEB_PACKAGE_TTP ?? (globalThis as any).trustedTypes?.createPolicy('amdLoader', { + this._amdPolicy = globalThis._VSCODE_WEB_PACKAGE_TTP ?? globalThis.trustedTypes?.createPolicy('amdLoader', { createScriptURL(value: string) { return value; } @@ -96,7 +101,7 @@ class AMDModuleImporter { if (this._state === AMDModuleImporterState.InitializedExternal) { return new Promise(resolve => { const tmpModuleId = generateUuid(); - (globalThis as any).define(tmpModuleId, [scriptSrc], function (moduleResult: T) { + globalThis.define(tmpModuleId, [scriptSrc], function (moduleResult: T) { resolve(moduleResult); }); }); @@ -157,7 +162,7 @@ class AMDModuleImporter { scriptElement.addEventListener('load', loadEventListener); scriptElement.addEventListener('error', errorEventListener); if (this._amdPolicy) { - scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string; + scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as unknown as string; } scriptElement.setAttribute('src', scriptSrc); window.document.getElementsByTagName('head')[0].appendChild(scriptElement); @@ -166,7 +171,7 @@ class AMDModuleImporter { private async _workerLoadScript(scriptSrc: string): Promise { if (this._amdPolicy) { - scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string; + scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as unknown as string; } await import(scriptSrc); return this._defineCalls.pop(); @@ -202,7 +207,7 @@ const cache = new Map>(); export async function importAMDNodeModule(nodeModuleName: string, pathInsideNodeModule: string, isBuilt?: boolean): Promise { if (isBuilt === undefined) { const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration; - isBuilt = Boolean((product ?? (globalThis as any).vscode?.context?.configuration()?.product)?.commit); + isBuilt = Boolean((product ?? globalThis.vscode?.context?.configuration()?.product)?.commit); } const nodeModulePath = pathInsideNodeModule ? `${nodeModuleName}/${pathInsideNodeModule}` : nodeModuleName; @@ -227,7 +232,7 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN export function resolveAmdNodeModulePath(nodeModuleName: string, pathInsideNodeModule: string): string { const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration; - const isBuilt = Boolean((product ?? (globalThis as any).vscode?.context?.configuration()?.product)?.commit); + const isBuilt = Boolean((product ?? globalThis.vscode?.context?.configuration()?.product)?.commit); const useASAR = (canASAR && isBuilt && !platform.isWeb); const nodeModulePath = `${nodeModuleName}/${pathInsideNodeModule}`; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 553c584a63d..97f35ccc34f 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -8,7 +8,7 @@ import { BrowserFeatures } from './canIUse.js'; import { IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent.js'; import { IMouseEvent, StandardMouseEvent } from './mouseEvent.js'; import { AbstractIdleValue, IntervalTimer, TimeoutTimer, _runWhenIdle, IdleDeadline } from '../common/async.js'; -import { onUnexpectedError } from '../common/errors.js'; +import { BugIndicatingError, onUnexpectedError } from '../common/errors.js'; import * as event from '../common/event.js'; import dompurify from './dompurify/dompurify.js'; import { KeyCode } from '../common/keyCodes.js'; @@ -19,8 +19,7 @@ import { URI } from '../common/uri.js'; import { hash } from '../common/hash.js'; import { CodeWindow, ensureCodeWindow, mainWindow } from './window.js'; import { isPointWithinTriangle } from '../common/numbers.js'; -export * from './domImpl/domObservable.js'; -export * from './domImpl/n.js'; +import { IObservable, derived, derivedOpts, IReader, observableValue } from '../common/observable.js'; export interface IRegisteredCodeWindow { readonly window: CodeWindow; @@ -2395,3 +2394,353 @@ export class SafeTriangle { return false; } } + + +export namespace n { + function nodeNs>(elementNs: string | undefined = undefined): DomTagCreateFn { + return (tag, attributes, children) => { + const className = attributes.class; + delete attributes.class; + const ref = attributes.ref; + delete attributes.ref; + const obsRef = attributes.obsRef; + delete attributes.obsRef; + + return new ObserverNodeWithElement(tag as any, ref, obsRef, elementNs, className, attributes, children); + }; + } + + function node, TKey extends keyof TMap>(tag: TKey, elementNs: string | undefined = undefined): DomCreateFn { + const f = nodeNs(elementNs) as any; + return (attributes, children) => { + return f(tag, attributes, children); + }; + } + + export const div: DomCreateFn = node('div'); + + export const elem = nodeNs(undefined); + + export const svg: DomCreateFn = node('svg', 'http://www.w3.org/2000/svg'); + + export const svgElem = nodeNs('http://www.w3.org/2000/svg'); + + export function ref(): IRefWithVal { + let value: T | undefined = undefined; + const result: IRef = function (val: T) { + value = val; + }; + Object.defineProperty(result, 'element', { + get() { + if (!value) { + throw new BugIndicatingError('Make sure the ref is set before accessing the element. Maybe wrong initialization order?'); + } + return value; + } + }); + return result as any; + } +} +type Value = T | IObservable; +type ValueOrList = Value | ValueOrList[]; +type ValueOrList2 = ValueOrList | ValueOrList>; +type HTMLOrSVGElement = HTMLElement | SVGElement; +type SVGElementTagNameMap2 = { + svg: SVGElement & { + width: number; + height: number; + transform: string; + viewBox: string; + fill: string; + }; + path: SVGElement & { + d: string; + stroke: string; + fill: string; + }; + linearGradient: SVGElement & { + id: string; + x1: string | number; + x2: string | number; + }; + stop: SVGElement & { + offset: string; + }; + rect: SVGElement & { + x: number; + y: number; + width: number; + height: number; + fill: string; + }; + defs: SVGElement; +}; +type DomTagCreateFn> = ( + tag: TTag, + attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, + children?: ChildNode +) => ObserverNode; +type DomCreateFn = ( + attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, + children?: ChildNode +) => ObserverNode; + +export type ChildNode = ValueOrList2; + +export type IRef = (value: T) => void; + +export interface IRefWithVal extends IRef { + readonly element: T; +} + +export abstract class ObserverNode { + private readonly _deriveds: (IObservable)[] = []; + + protected readonly _element: T; + + constructor( + tag: string, + ref: IRef | undefined, + obsRef: IRef | null> | undefined, + ns: string | undefined, + className: ValueOrList | undefined, + attributes: ElementAttributeKeys, + children: ChildNode + ) { + this._element = (ns ? document.createElementNS(ns, tag) : document.createElement(tag)) as unknown as T; + if (ref) { + ref(this._element); + } + if (obsRef) { + this._deriveds.push(derived((_reader) => { + obsRef(this as unknown as ObserverNodeWithElement); + _reader.store.add({ + dispose: () => { + obsRef(null); + } + }); + })); + } + + if (className) { + if (hasObservable(className)) { + this._deriveds.push(derived(this, reader => { + /** @description set.class */ + setClassName(this._element, getClassName(className, reader)); + })); + } else { + setClassName(this._element, getClassName(className, undefined)); + } + } + + for (const [key, value] of Object.entries(attributes)) { + if (key === 'style') { + for (const [cssKey, cssValue] of Object.entries(value)) { + const key = camelCaseToHyphenCase(cssKey); + if (isObservable(cssValue)) { + this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.style.${key}` }, reader => { + this._element.style.setProperty(key, convertCssValue(cssValue.read(reader))); + })); + } else { + this._element.style.setProperty(key, convertCssValue(cssValue)); + } + } + } else if (key === 'tabIndex') { + if (isObservable(value)) { + this._deriveds.push(derived(this, reader => { + /** @description set.tabIndex */ + this._element.tabIndex = value.read(reader) as any; + })); + } else { + this._element.tabIndex = value; + } + } else if (key.startsWith('on')) { + (this._element as any)[key] = value; + } else { + if (isObservable(value)) { + this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.${key}` }, reader => { + setOrRemoveAttribute(this._element, key, value.read(reader)); + })); + } else { + setOrRemoveAttribute(this._element, key, value); + } + } + } + + if (children) { + function getChildren(reader: IReader | undefined, children: ValueOrList2): (HTMLOrSVGElement | string)[] { + if (isObservable(children)) { + return getChildren(reader, children.read(reader)); + } + if (Array.isArray(children)) { + return children.flatMap(c => getChildren(reader, c)); + } + if (children instanceof ObserverNode) { + if (reader) { + children.readEffect(reader); + } + return [children._element]; + } + if (children) { + return [children]; + } + return []; + } + + const d = derived(this, reader => { + /** @description set.children */ + this._element.replaceChildren(...getChildren(reader, children)); + }); + this._deriveds.push(d); + if (!childrenIsObservable(children)) { + d.get(); + } + } + } + + readEffect(reader: IReader | undefined): void { + for (const d of this._deriveds) { + d.read(reader); + } + } + + keepUpdated(store: DisposableStore): ObserverNodeWithElement { + derived(reader => { + /** update */ + this.readEffect(reader); + }).recomputeInitiallyAndOnChange(store); + return this as unknown as ObserverNodeWithElement; + } + + /** + * Creates a live element that will keep the element updated as long as the returned object is not disposed. + */ + toDisposableLiveElement() { + const store = new DisposableStore(); + this.keepUpdated(store); + return new LiveElement(this._element, store); + } +} +function setClassName(domNode: HTMLOrSVGElement, className: string) { + if (isSVGElement(domNode)) { + domNode.setAttribute('class', className); + } else { + domNode.className = className; + } +} +function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val: T) => void): void { + if (isObservable(value)) { + cb(value.read(reader)); + return; + } + if (Array.isArray(value)) { + for (const v of value) { + resolve(v, reader, cb); + } + return; + } + cb(value as any); +} +function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { + let result = ''; + resolve(className, reader, val => { + if (val) { + if (result.length === 0) { + result = val; + } else { + result += ' ' + val; + } + } + }); + return result; +} +function hasObservable(value: ValueOrList): boolean { + if (isObservable(value)) { + return true; + } + if (Array.isArray(value)) { + return value.some(v => hasObservable(v)); + } + return false; +} +function convertCssValue(value: any): string { + if (typeof value === 'number') { + return value + 'px'; + } + return value; +} +function childrenIsObservable(children: ValueOrList2): boolean { + if (isObservable(children)) { + return true; + } + if (Array.isArray(children)) { + return children.some(c => childrenIsObservable(c)); + } + return false; +} + +export class LiveElement { + constructor( + public readonly element: T, + private readonly _disposable: IDisposable + ) { } + + dispose() { + this._disposable.dispose(); + } +} + +export class ObserverNodeWithElement extends ObserverNode { + public get element() { + return this._element; + } + + private _isHovered: IObservable | undefined = undefined; + + get isHovered(): IObservable { + if (!this._isHovered) { + const hovered = observableValue('hovered', false); + this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined)); + this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined)); + this._isHovered = hovered; + } + return this._isHovered; + } + + private _didMouseMoveDuringHover: IObservable | undefined = undefined; + + get didMouseMoveDuringHover(): IObservable { + if (!this._didMouseMoveDuringHover) { + let _hovering = false; + const hovered = observableValue('didMouseMoveDuringHover', false); + this._element.addEventListener('mouseenter', (_e) => { + _hovering = true; + }); + this._element.addEventListener('mousemove', (_e) => { + if (_hovering) { + hovered.set(true, undefined); + } + }); + this._element.addEventListener('mouseleave', (_e) => { + _hovering = false; + hovered.set(false, undefined); + }); + this._didMouseMoveDuringHover = hovered; + } + return this._didMouseMoveDuringHover; + } +} +function setOrRemoveAttribute(element: HTMLOrSVGElement, key: string, value: unknown) { + if (value === null || value === undefined) { + element.removeAttribute(camelCaseToHyphenCase(key)); + } else { + element.setAttribute(camelCaseToHyphenCase(key), String(value)); + } +} + +function isObservable(obj: unknown): obj is IObservable { + return !!obj && (>obj).read !== undefined && (>obj).reportChanges !== undefined; +} +type ElementAttributeKeys = Partial<{ + [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? ElementAttributeKeys : Value; +}>; diff --git a/src/vs/base/browser/domImpl/domObservable.ts b/src/vs/base/browser/domImpl/domObservable.ts deleted file mode 100644 index c66a031de67..00000000000 --- a/src/vs/base/browser/domImpl/domObservable.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DisposableStore, IDisposable } from '../../common/lifecycle.js'; -import { autorun, IObservable } from '../../common/observable.js'; -import { createStyleSheet2 } from '../domStylesheets.js'; - -export function createStyleSheetFromObservable(css: IObservable): IDisposable { - const store = new DisposableStore(); - const w = store.add(createStyleSheet2()); - store.add(autorun(reader => { - w.setStyle(css.read(reader)); - })); - return store; -} diff --git a/src/vs/base/browser/domImpl/n.ts b/src/vs/base/browser/domImpl/n.ts deleted file mode 100644 index a3c7653a51c..00000000000 --- a/src/vs/base/browser/domImpl/n.ts +++ /dev/null @@ -1,373 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BugIndicatingError } from '../../common/errors.js'; -import { DisposableStore, IDisposable } from '../../common/lifecycle.js'; -import { derived, derivedOpts, IObservable, IReader, observableValue } from '../../common/observable.js'; -import { isSVGElement } from '../dom.js'; - -export namespace n { - function nodeNs>(elementNs: string | undefined = undefined): DomTagCreateFn { - return (tag, attributes, children) => { - const className = attributes.class; - delete attributes.class; - const ref = attributes.ref; - delete attributes.ref; - const obsRef = attributes.obsRef; - delete attributes.obsRef; - - return new ObserverNodeWithElement(tag as any, ref, obsRef, elementNs, className, attributes, children); - }; - } - - function node, TKey extends keyof TMap>(tag: TKey, elementNs: string | undefined = undefined): DomCreateFn { - const f = nodeNs(elementNs) as any; - return (attributes, children) => { - return f(tag, attributes, children); - }; - } - - export const div: DomCreateFn = node('div'); - - export const elem = nodeNs(undefined); - - export const svg: DomCreateFn = node('svg', 'http://www.w3.org/2000/svg'); - - export const svgElem = nodeNs('http://www.w3.org/2000/svg'); - - export function ref(): IRefWithVal { - let value: T | undefined = undefined; - const result: IRef = function (val: T) { - value = val; - }; - Object.defineProperty(result, 'element', { - get() { - if (!value) { - throw new BugIndicatingError('Make sure the ref is set before accessing the element. Maybe wrong initialization order?'); - } - return value; - } - }); - return result as any; - } -} - -type Value = T | IObservable; -type ValueOrList = Value | ValueOrList[]; -type ValueOrList2 = ValueOrList | ValueOrList>; -type Element = HTMLElement | SVGElement; -type SVGElementTagNameMap2 = { - svg: SVGElement & { - width: number; - height: number; - transform: string; - viewBox: string; - fill: string; - }; - path: SVGElement & { - d: string; - stroke: string; - fill: string; - }; - linearGradient: SVGElement & { - id: string; - x1: string | number; - x2: string | number; - }; - stop: SVGElement & { - offset: string; - }; - rect: SVGElement & { - x: number; - y: number; - width: number; - height: number; - fill: string; - }; - defs: SVGElement; -}; - -type DomTagCreateFn> = ( - tag: TTag, - attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, - children?: ChildNode -) => ObserverNode; - -type DomCreateFn = ( - attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef; obsRef?: IRef | null> }, - children?: ChildNode -) => ObserverNode; - -export type ChildNode = ValueOrList2; - -export type IRef = (value: T) => void; - -export interface IRefWithVal extends IRef { - readonly element: T; -} - -export abstract class ObserverNode { - private readonly _deriveds: (IObservable)[] = []; - - protected readonly _element: T; - - constructor( - tag: string, - ref: IRef | undefined, - obsRef: IRef | null> | undefined, - ns: string | undefined, - className: ValueOrList | undefined, - attributes: ElementAttributeKeys, - children: ChildNode - ) { - this._element = (ns ? document.createElementNS(ns, tag) : document.createElement(tag)) as unknown as T; - if (ref) { - ref(this._element); - } - if (obsRef) { - this._deriveds.push(derived((_reader) => { - obsRef(this as unknown as ObserverNodeWithElement); - _reader.store.add({ - dispose: () => { - obsRef(null); - } - }); - })); - } - - if (className) { - if (hasObservable(className)) { - this._deriveds.push(derived(this, reader => { - /** @description set.class */ - setClassName(this._element, getClassName(className, reader)); - })); - } else { - setClassName(this._element, getClassName(className, undefined)); - } - } - - for (const [key, value] of Object.entries(attributes)) { - if (key === 'style') { - for (const [cssKey, cssValue] of Object.entries(value)) { - const key = camelCaseToHyphenCase(cssKey); - if (isObservable(cssValue)) { - this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.style.${key}` }, reader => { - this._element.style.setProperty(key, convertCssValue(cssValue.read(reader))); - })); - } else { - this._element.style.setProperty(key, convertCssValue(cssValue)); - } - } - } else if (key === 'tabIndex') { - if (isObservable(value)) { - this._deriveds.push(derived(this, reader => { - /** @description set.tabIndex */ - this._element.tabIndex = value.read(reader) as any; - })); - } else { - this._element.tabIndex = value; - } - } else if (key.startsWith('on')) { - (this._element as any)[key] = value; - } else { - if (isObservable(value)) { - this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.${key}` }, reader => { - setOrRemoveAttribute(this._element, key, value.read(reader)); - })); - } else { - setOrRemoveAttribute(this._element, key, value); - } - } - } - - if (children) { - function getChildren(reader: IReader | undefined, children: ValueOrList2): (Element | string)[] { - if (isObservable(children)) { - return getChildren(reader, children.read(reader)); - } - if (Array.isArray(children)) { - return children.flatMap(c => getChildren(reader, c)); - } - if (children instanceof ObserverNode) { - if (reader) { - children.readEffect(reader); - } - return [children._element]; - } - if (children) { - return [children]; - } - return []; - } - - const d = derived(this, reader => { - /** @description set.children */ - this._element.replaceChildren(...getChildren(reader, children)); - }); - this._deriveds.push(d); - if (!childrenIsObservable(children)) { - d.get(); - } - } - } - - readEffect(reader: IReader | undefined): void { - for (const d of this._deriveds) { - d.read(reader); - } - } - - keepUpdated(store: DisposableStore): ObserverNodeWithElement { - derived(reader => { - /** update */ - this.readEffect(reader); - }).recomputeInitiallyAndOnChange(store); - return this as unknown as ObserverNodeWithElement; - } - - /** - * Creates a live element that will keep the element updated as long as the returned object is not disposed. - */ - toDisposableLiveElement() { - const store = new DisposableStore(); - this.keepUpdated(store); - return new LiveElement(this._element, store); - } -} - -function setClassName(domNode: Element, className: string) { - if (isSVGElement(domNode)) { - domNode.setAttribute('class', className); - } else { - domNode.className = className; - } -} - -function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val: T) => void): void { - if (isObservable(value)) { - cb(value.read(reader)); - return; - } - if (Array.isArray(value)) { - for (const v of value) { - resolve(v, reader, cb); - } - return; - } - cb(value as any); -} - -function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { - let result = ''; - resolve(className, reader, val => { - if (val) { - if (result.length === 0) { - result = val; - } else { - result += ' ' + val; - } - } - }); - return result; -} - -function hasObservable(value: ValueOrList): boolean { - if (isObservable(value)) { - return true; - } - if (Array.isArray(value)) { - return value.some(v => hasObservable(v)); - } - return false; -} - -function convertCssValue(value: any): string { - if (typeof value === 'number') { - return value + 'px'; - } - return value; -} - -function childrenIsObservable(children: ValueOrList2): boolean { - if (isObservable(children)) { - return true; - } - if (Array.isArray(children)) { - return children.some(c => childrenIsObservable(c)); - } - return false; -} - -export class LiveElement { - constructor( - public readonly element: T, - private readonly _disposable: IDisposable - ) { } - - dispose() { - this._disposable.dispose(); - } -} - -export class ObserverNodeWithElement extends ObserverNode { - public get element() { - return this._element; - } - - private _isHovered: IObservable | undefined = undefined; - - get isHovered(): IObservable { - if (!this._isHovered) { - const hovered = observableValue('hovered', false); - this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined)); - this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined)); - this._isHovered = hovered; - } - return this._isHovered; - } - - private _didMouseMoveDuringHover: IObservable | undefined = undefined; - - get didMouseMoveDuringHover(): IObservable { - if (!this._didMouseMoveDuringHover) { - let _hovering = false; - const hovered = observableValue('didMouseMoveDuringHover', false); - this._element.addEventListener('mouseenter', (_e) => { - _hovering = true; - }); - this._element.addEventListener('mousemove', (_e) => { - if (_hovering) { - hovered.set(true, undefined); - } - }); - this._element.addEventListener('mouseleave', (_e) => { - _hovering = false; - hovered.set(false, undefined); - }); - this._didMouseMoveDuringHover = hovered; - } - return this._didMouseMoveDuringHover; - } -} - -function setOrRemoveAttribute(element: Element, key: string, value: unknown) { - if (value === null || value === undefined) { - element.removeAttribute(camelCaseToHyphenCase(key)); - } else { - element.setAttribute(camelCaseToHyphenCase(key), String(value)); - } -} - -function camelCaseToHyphenCase(str: string) { - return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); -} - -function isObservable(obj: unknown): obj is IObservable { - return !!obj && (>obj).read !== undefined && (>obj).reportChanges !== undefined; -} - -type ElementAttributeKeys = Partial<{ - [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? ElementAttributeKeys : Value; -}>; diff --git a/src/vs/base/browser/domStylesheets.ts b/src/vs/base/browser/domStylesheets.ts index ebc249c6608..76a71c5402d 100644 --- a/src/vs/base/browser/domStylesheets.ts +++ b/src/vs/base/browser/domStylesheets.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js'; +import { autorun, IObservable } from '../common/observable.js'; import { getWindows, sharedMutationObserver } from './dom.js'; import { mainWindow } from './window.js'; @@ -166,3 +167,12 @@ export function removeCSSRulesContainingSelector(ruleName: string, style = getSh function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { return typeof (rule as CSSStyleRule).selectorText === 'string'; } + +export function createStyleSheetFromObservable(css: IObservable): IDisposable { + const store = new DisposableStore(); + const w = store.add(createStyleSheet2()); + store.add(autorun(reader => { + w.setStyle(css.read(reader)); + })); + return store; +} diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 2cb748afcdf..54e6fd71b87 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -323,7 +323,7 @@ function activateLink(markdown: IMarkdownString, options: MarkdownRenderOptions, } function uriMassage(markdown: IMarkdownString, part: string): string { - let data: any; + let data: unknown; try { data = parse(decodeURIComponent(part)); } catch (e) { diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index a4cb3361f9d..01e522130a9 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -32,7 +32,7 @@ export interface IBreadcrumbsItemEvent { type: 'select' | 'focus'; item: BreadcrumbsItem; node: HTMLElement; - payload: any; + payload: unknown; } export class BreadcrumbsWidget { diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4c099bd9271..88916fe998c 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index cd7aabf6cfb..882c2e980fb 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -749,7 +749,7 @@ export interface IViewDeserializer { export interface ISerializedLeafNode { type: 'leaf'; - data: any; + data: unknown; size: number; visible?: boolean; maximized?: boolean; diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 09452c14992..bf3d737867f 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -145,7 +145,7 @@ export interface IViewDeserializer { export interface ISerializedLeafNode { type: 'leaf'; - data: any; + data: unknown; size: number; visible?: boolean; maximized?: boolean; diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 736a1d727a9..cb486cfaaba 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -77,7 +77,7 @@ class PagedAccessibilityProvider implements IListAccessibilityProvider ) { } - getWidgetAriaLabel(): string { + getWidgetAriaLabel() { return this.accessibilityProvider.getWidgetAriaLabel(); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index e464d9fca7e..96865619e88 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -856,7 +856,7 @@ export interface IStyleController { export interface IListAccessibilityProvider extends IListViewAccessibilityProvider { getAriaLabel(element: T): string | IObservable | null; - getWidgetAriaLabel(): string; + getWidgetAriaLabel(): string | IObservable; getWidgetRole?(): AriaRole; getAriaLevel?(element: T): number | undefined; onDidChangeActiveDescendant?: Event; @@ -1530,7 +1530,12 @@ export class List implements ISpliceable, IDisposable { this.onDidChangeSelection(this._onSelectionChange, this, this.disposables); if (this.accessibilityProvider) { - this.ariaLabel = this.accessibilityProvider.getWidgetAriaLabel(); + const ariaLabel = this.accessibilityProvider.getWidgetAriaLabel(); + const observable = (ariaLabel && typeof ariaLabel !== 'string') ? ariaLabel : constObservable(ariaLabel); + + this.disposables.add(autorun(reader => { + this.ariaLabel = reader.readObservable(observable); + })); } if (this._options.multipleSelectionSupport !== false) { diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index aa2072ff03a..93fd6f82c06 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -55,10 +55,12 @@ export class ToggleActionViewItem extends BaseActionViewItem { constructor(context: unknown, action: IAction, options: IActionViewItemOptions) { super(context, action, options); + const title = (this.options).keybinding ? + `${this._action.label} (${(this.options).keybinding})` : this._action.label; this.toggle = this._register(new Toggle({ actionClassName: this._action.class, isChecked: !!this._action.checked, - title: (this.options).keybinding ? `${this._action.label} (${(this.options).keybinding})` : this._action.label, + title, notFocusable: true, inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, @@ -94,6 +96,12 @@ export class ToggleActionViewItem extends BaseActionViewItem { this.toggle.checked = !!this._action.checked; } + protected override updateLabel(): void { + const title = (this.options).keybinding ? + `${this._action.label} (${(this.options).keybinding})` : this._action.label; + this.toggle.setTitle(title); + } + override focus(): void { this.toggle.domNode.tabIndex = 0; this.toggle.focus(); diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 0305b5c4524..2f16dbccda4 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -624,35 +624,7 @@ function getActualStartIndex(array: T[], start: number): number { return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length); } -/** - * Utility that helps to pick a property from an object. - * - * ## Examples - * - * ```typescript - * interface IObject = { - * a: number, - * b: string, - * }; - * - * const list: IObject[] = [ - * { a: 1, b: 'foo' }, - * { a: 2, b: 'bar' }, - * ]; - * - * assert.deepStrictEqual( - * list.map(pick('a')), - * [1, 2], - * ); - * ``` - */ -export const pick = ( - key: TKeyName, -) => { - return (obj: TObject): TObject[TKeyName] => { - return obj[key]; - }; -}; + /** * When comparing two values, diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 81f556b1364..fb72d242be6 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -1459,7 +1459,8 @@ export let runWhenGlobalIdle: (callback: (idle: IdleDeadline) => void, timeout?: export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable; (function () { - if (typeof globalThis.requestIdleCallback !== 'function' || typeof globalThis.cancelIdleCallback !== 'function') { + const safeGlobal: any = globalThis; + if (typeof safeGlobal.requestIdleCallback !== 'function' || typeof safeGlobal.cancelIdleCallback !== 'function') { _runWhenIdle = (_targetWindow, runner, timeout?) => { setTimeout0(() => { if (disposed) { @@ -1485,7 +1486,7 @@ export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) }; }; } else { - _runWhenIdle = (targetWindow: IdleApi, runner, timeout?) => { + _runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => { const handle: number = targetWindow.requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); let disposed = false; return { @@ -1742,7 +1743,7 @@ export class DeferredPromise { private completeCallback!: ValueCallback; private errorCallback!: (err: unknown) => void; - private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; + private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T }; public get isRejected() { return this.outcome?.outcome === DeferredOutcome.Rejected; diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 39c2f623fbf..831357fec08 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -6,13 +6,20 @@ import { Lazy } from './lazy.js'; import * as streams from './stream.js'; -declare const Buffer: any; +interface NodeBuffer { + allocUnsafe(size: number): Uint8Array; + isBuffer(obj: any): obj is NodeBuffer; + from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array; + from(data: string): Uint8Array; +} + +declare const Buffer: NodeBuffer; const hasBuffer = (typeof Buffer !== 'undefined'); const indexOfTable = new Lazy(() => new Uint8Array(256)); -let textEncoder: TextEncoder | null; -let textDecoder: TextDecoder | null; +let textEncoder: { encode: (input: string) => Uint8Array } | null; +let textDecoder: { decode: (input: Uint8Array) => string } | null; export class VSBuffer { @@ -93,6 +100,10 @@ export class VSBuffer { return ret; } + static isNativeBuffer(buffer: unknown): boolean { + return hasBuffer && Buffer.isBuffer(buffer); + } + readonly buffer: Uint8Array; readonly byteLength: number; diff --git a/src/vs/base/common/dataTransfer.ts b/src/vs/base/common/dataTransfer.ts index 3ce5770c57f..9f504a20c5a 100644 --- a/src/vs/base/common/dataTransfer.ts +++ b/src/vs/base/common/dataTransfer.ts @@ -19,7 +19,7 @@ export interface IDataTransferItem { id?: string; asString(): Thenable; asFile(): IDataTransferFile | undefined; - value: any; + value: unknown; } export function createStringDataTransferItem(stringOrPromise: string | Promise, id?: string): IDataTransferItem { diff --git a/src/vs/base/common/decorators/logTime.ts b/src/vs/base/common/decorators/logTime.ts deleted file mode 100644 index 1ce87e4cdaf..00000000000 --- a/src/vs/base/common/decorators/logTime.ts +++ /dev/null @@ -1,171 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assertDefined } from '../types.js'; - -/** - * Type for a function that logs a message. - */ -export type TLogFunction = (message: string, ...args: any[]) => void; - -/** - * Type for an object that contains a `logger` property - * with the logging methods. - */ -type TObjectWithLogFunction = T & { logTime: TLogFunction }; - -/** - * Decorator allows to log execution time of any method of a class. - * The class must have the `logTime` method that provides logs - * a provided message. - * - * The decorated method can be asynchronous or synchronous, but - * the timing message is logged only if it finishes *successfully*. - * - * ## Examples - * - * ```typescript - * class MyClass { - * public readonly logTime: TLogFunction; - - * constructor( - * // because we have the interface restrictions on the class - * // which does not support 'private'/'protected' fields, we are - * // forced to use the 'public' modifier here - * \@ILogService public readonly logService: ILogService, - * ) { - * this.logTime = logService.info.bind(logService); - * } - * - * @logTime() - * public async myMethod(): Promise { - * // some artificial delay - * await new Promise((resolve) => setTimeout(resolve, 10)); - * - * return 'haalou!'; - * } - * } - * - * const myObject = instantiationService.createInstance(MyClass); - * - * // once the method completes successfully, the information - * // message '[MyClass.myMethod] took 10.00 ms' is logged - * const result = await myObject.myMethod(); - * - * assert.strictEqual( - * result, - * 'haalou!', - * 'Must yield original return value', - * ); - * ``` - */ -export function logTime() { - return function logExecutionTimeDecorator< - TObject extends TObjectWithLogFunction, - >( - _proto: TObject, - methodName: string, - descriptor: TypedPropertyDescriptor<(...args: any[]) => any | Promise>, - ) { - const originalMethod = descriptor.value; - - assertDefined( - originalMethod, - `Method '${methodName}' is not defined.`, - ); - - // override the decorated method with the one that logs - // a timing message after the original method finishes execution - descriptor.value = function ( - this: TObject, - ...args: Parameters - ): ReturnType { - return logExecutionTime( - `${this.constructor.name}.${methodName}`, - originalMethod.bind(this, ...args), - this.logTime.bind(this), - ); - }; - - return descriptor; - }; -} - -/** - * Helper allows to log execution time of code block or function. - * - * The code block or function can be asynchronous or synchronous, but - * the timing message is logged only if it finishes *successfully*. - * - * ## Examples - * - * ```typescript - * const result = logExecutionTime( - * 'my asynchronous block', - * async () => { - * // some artificial delay - * await new Promise((resolve) => setTimeout(resolve, 10)); - * - * return 'haalou!'; - * }, - * this.logService.info, - * } - * - * // once the callback completes successfully, the information - * // message '[MyClass.myMethod] took 10.00 ms' is logged - * assert.strictEqual( - * result, - * 'haalou!', - * 'Must yield original return value', - * ); - * ``` - */ -export const logExecutionTime = ( - blockName: string, - callback: () => T | Promise, - logger: TLogFunction, -): ReturnType => { - const startTime = performance.now(); - const result = callback(); - const syncTimeMs = performance.now() - startTime; - - // handle asynchronous decorated methods - if (result instanceof Promise) { - return result.then((resolved) => { - const asyncTimeMs = performance.now() - startTime; - - log( - blockName, - asyncTimeMs, - logger, - ); - return resolved; - }); - } - - // handle synchronous decorated methods - log( - blockName, - syncTimeMs, - logger, - ); - - return result; -}; - -/** - * Internal helper to log the timing message with - * provided details and logger. - */ -const log = ( - methodName: string, - timeMs: number, - logger: TLogFunction, -): void => { - return logger( - // allow-any-unicode-next-line - `[⏱][${methodName}] took ${timeMs.toFixed(2)} ms`, - ); -}; diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index bd0fc90ee3d..5ec4c00022b 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -325,7 +325,7 @@ export namespace Event { * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. */ - export function accumulate(event: Event, delay: number = 0, disposable?: DisposableStore): Event { + export function accumulate(event: Event, delay: number | typeof MicrotaskDelay = 0, disposable?: DisposableStore): Event { return Event.debounce(event, (last, e) => { if (!last) { return [e]; diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 1057364d7fc..276f076788d 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -311,9 +311,7 @@ const NULL = function (): string | null { * * See {@link FALSE} and {@link NULL}. */ -export const isEmptyPattern = ( - pattern: ParsedPattern | ParsedExpression, -): pattern is (typeof FALSE | typeof NULL) => { +export function isEmptyPattern(pattern: ParsedPattern | ParsedExpression): pattern is (typeof FALSE | typeof NULL) { if (pattern === FALSE) { return true; } @@ -323,7 +321,7 @@ export const isEmptyPattern = ( } return false; -}; +} function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): ParsedStringPattern { if (!arg1) { diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts index c699dbcb569..b3ae24ae69a 100644 --- a/src/vs/base/common/jsonEdit.ts +++ b/src/vs/base/common/jsonEdit.ts @@ -11,7 +11,7 @@ export function removeProperty(text: string, path: JSONPath, formattingOptions: return setProperty(text, path, undefined, formattingOptions); } -export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] { +export function setProperty(text: string, originalPath: JSONPath, value: unknown, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] { const path = originalPath.slice(); const errors: ParseError[] = []; const root = parseTree(text, errors); diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 193f687da24..f640014499e 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -84,6 +84,9 @@ export namespace Schemas { /** Scheme used for the chat input editor. */ export const vscodeChatSesssion = 'vscode-chat-editor'; + /** Scheme used for the chat input part */ + export const vscodeChatInput = 'chatSessionInput'; + /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) */ diff --git a/src/vs/base/common/oauth.ts b/src/vs/base/common/oauth.ts index a32f3409884..005f5ad1fbc 100644 --- a/src/vs/base/common/oauth.ts +++ b/src/vs/base/common/oauth.ts @@ -8,9 +8,44 @@ import { decodeBase64 } from './buffer.js'; const WELL_KNOWN_ROUTE = '/.well-known'; export const AUTH_PROTECTED_RESOURCE_METADATA_DISCOVERY_PATH = `${WELL_KNOWN_ROUTE}/oauth-protected-resource`; export const AUTH_SERVER_METADATA_DISCOVERY_PATH = `${WELL_KNOWN_ROUTE}/oauth-authorization-server`; +export const AUTH_SCOPE_SEPARATOR = ' '; //#region types +/** + * Base OAuth 2.0 error codes as specified in RFC 6749. + */ +export const enum AuthorizationErrorType { + InvalidRequest = 'invalid_request', + InvalidClient = 'invalid_client', + InvalidGrant = 'invalid_grant', + UnauthorizedClient = 'unauthorized_client', + UnsupportedGrantType = 'unsupported_grant_type', + InvalidScope = 'invalid_scope' +} + +/** + * Device authorization grant specific error codes as specified in RFC 8628 section 3.5. + */ +export const enum AuthorizationDeviceCodeErrorType { + /** + * The authorization request is still pending as the end user hasn't completed the user interaction steps. + */ + AuthorizationPending = 'authorization_pending', + /** + * A variant of "authorization_pending", polling should continue but interval must be increased by 5 seconds. + */ + SlowDown = 'slow_down', + /** + * The authorization request was denied. + */ + AccessDenied = 'access_denied', + /** + * The "device_code" has expired and the device authorization session has concluded. + */ + ExpiredToken = 'expired_token' +} + /** * Metadata about a protected resource. */ @@ -395,18 +430,11 @@ export interface IAuthorizationDeviceResponse { * Error response from the token endpoint when using device authorization grant. * As defined in RFC 8628 section 3.5. */ -export interface IAuthorizationDeviceTokenErrorResponse { +export interface IAuthorizationErrorResponse { /** * REQUIRED. Error code as specified in OAuth 2.0 or in RFC 8628 section 3.5. - * Standard OAuth 2.0 error codes plus: - * - "authorization_pending": The authorization request is still pending as the end user hasn't completed the user interaction steps - * - "slow_down": A variant of "authorization_pending", polling should continue but interval must be increased by 5 seconds - * - "access_denied": The authorization request was denied - * - "expired_token": The "device_code" has expired and the device authorization session has concluded */ - error: 'invalid_request' | 'invalid_client' | 'invalid_grant' | 'unauthorized_client' | - 'unsupported_grant_type' | 'invalid_scope' | 'authorization_pending' | - 'slow_down' | 'access_denied' | 'expired_token' | string; + error: AuthorizationErrorType | string; /** * OPTIONAL. Human-readable description of the error. @@ -419,6 +447,17 @@ export interface IAuthorizationDeviceTokenErrorResponse { error_uri?: string; } +/** + * Error response from the token endpoint when using device authorization grant. + * As defined in RFC 8628 section 3.5. + */ +export interface IAuthorizationDeviceTokenErrorResponse extends IAuthorizationErrorResponse { + /** + * REQUIRED. Error code as specified in OAuth 2.0 or in RFC 8628 section 3.5. + */ + error: AuthorizationErrorType | AuthorizationDeviceCodeErrorType | string; +} + export interface IAuthorizationJWTClaims { /** * REQUIRED. JWT ID. Unique identifier for the token. @@ -606,12 +645,12 @@ export function isAuthorizationDeviceResponse(obj: unknown): obj is IAuthorizati return response.device_code !== undefined && response.user_code !== undefined && response.verification_uri !== undefined && response.expires_in !== undefined; } -export function isAuthorizationDeviceTokenErrorResponse(obj: unknown): obj is IAuthorizationDeviceTokenErrorResponse { +export function isAuthorizationErrorResponse(obj: unknown): obj is IAuthorizationErrorResponse { if (typeof obj !== 'object' || obj === null) { return false; } - const response = obj as IAuthorizationDeviceTokenErrorResponse; - return response.error !== undefined && response.error_description !== undefined; + const response = obj as IAuthorizationErrorResponse; + return response.error !== undefined; } //#endregion @@ -646,7 +685,7 @@ export function getMetadataWithDefaultValues(metadata: IAuthorizationServerMetad * the spec and require an exact match. */ export const DEFAULT_AUTH_FLOW_PORT = 33418; -export async function fetchDynamicRegistration(registrationEndpoint: string, clientName: string): Promise { +export async function fetchDynamicRegistration(registrationEndpoint: string, clientName: string, scopes?: string[]): Promise { const response = await fetch(registrationEndpoint, { method: 'POST', headers: { @@ -669,6 +708,7 @@ export async function fetchDynamicRegistration(registrationEndpoint: string, cli `http://localhost:${DEFAULT_AUTH_FLOW_PORT}/`, `http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/` ], + scope: scopes?.join(AUTH_SCOPE_SEPARATOR), token_endpoint_auth_method: 'none' }) }); diff --git a/src/vs/base/common/observableInternal/experimental/utils.ts b/src/vs/base/common/observableInternal/experimental/utils.ts index 3a64aae46f2..a9038ac0946 100644 --- a/src/vs/base/common/observableInternal/experimental/utils.ts +++ b/src/vs/base/common/observableInternal/experimental/utils.ts @@ -22,7 +22,7 @@ export function latestChangedValue[]>(owner: DebugOwn } let hasLastChangedValue = false; - let lastChangedValue: any = undefined; + let lastChangedValue: unknown = undefined; const result = observableFromEvent(owner, cb => { const store = new DisposableStore(); diff --git a/src/vs/base/common/observableInternal/map.ts b/src/vs/base/common/observableInternal/map.ts index 9959bd1a1e5..1db8c9ebb26 100644 --- a/src/vs/base/common/observableInternal/map.ts +++ b/src/vs/base/common/observableInternal/map.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValueOpts, IObservable, ITransaction } from '../observable.js'; +import { IObservable, ITransaction } from '../observable.js'; +import { observableValueOpts } from './observables/observableValueOpts.js'; + export class ObservableMap implements Map { private readonly _data = new Map(); diff --git a/src/vs/base/common/observableInternal/set.ts b/src/vs/base/common/observableInternal/set.ts index 10a624dcbaa..294cdc73ca4 100644 --- a/src/vs/base/common/observableInternal/set.ts +++ b/src/vs/base/common/observableInternal/set.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValueOpts, IObservable, ITransaction } from '../observable.js'; - +import { IObservable, ITransaction } from '../observable.js'; +import { observableValueOpts } from './observables/observableValueOpts.js'; export class ObservableSet implements Set { diff --git a/src/vs/base/common/performance.ts b/src/vs/base/common/performance.ts index 26cd624136b..ab97b75fb7e 100644 --- a/src/vs/base/common/performance.ts +++ b/src/vs/base/common/performance.ts @@ -29,6 +29,27 @@ function _definePolyfillMarks(timeOrigin?: number) { declare const process: INodeProcess; +interface IPerformanceEntry { + readonly name: string; + readonly startTime: number; +} + +interface IPerformanceTiming { + readonly navigationStart?: number; + readonly redirectStart?: number; + readonly fetchStart?: number; +} + +interface IPerformance { + mark(name: string, markOptions?: { startTime?: number }): void; + getEntriesByType(type: string): IPerformanceEntry[]; + readonly timeOrigin: number; + readonly timing: IPerformanceTiming; + readonly nodeTiming?: any; +} + +declare const performance: IPerformance; + function _define() { // Identify browser environment when following property is not present @@ -53,7 +74,7 @@ function _define() { if (typeof timeOrigin !== 'number') { // safari: there is no timerOrigin but in renderers there is the timing-property // see https://bugs.webkit.org/show_bug.cgi?id=174862 - timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart; + timeOrigin = (performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart) ?? 0; } const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }]; for (const entry of performance.getEntriesByType('mark')) { diff --git a/src/vs/base/common/policy.ts b/src/vs/base/common/policy.ts index 8600c43854e..befbff12794 100644 --- a/src/vs/base/common/policy.ts +++ b/src/vs/base/common/policy.ts @@ -28,7 +28,10 @@ export interface IPolicy { readonly previewFeature?: boolean; /** - * Default value when enabled. Default is `false`. + * Default value for a 'previewFeature' policy. Default is `false`. + * Remarks: + * A default value is only relevant when previewFeature is `true`. + * In all other instances, a value is required when setting a policy. */ readonly defaultValue?: string | number | boolean; } diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index c3df2a13a97..653a85bb523 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -11,7 +11,7 @@ export interface IBuiltInExtension { readonly name: string; readonly version: string; readonly repo: string; - readonly metadata: any; + readonly metadata: unknown; } export interface IProductWalkthrough { diff --git a/src/vs/base/common/stopwatch.ts b/src/vs/base/common/stopwatch.ts index ca3cb6388bc..6b62b72aea9 100644 --- a/src/vs/base/common/stopwatch.ts +++ b/src/vs/base/common/stopwatch.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// fake definition so that the valid layers check won't trip on this declare const globalThis: { performance: { now(): number } }; - const performanceNow = globalThis.performance.now.bind(globalThis.performance); export class StopWatch { diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index c895259ac9e..b08d15edc6c 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from './uri.js'; import { assert } from './assert.js'; /** @@ -106,7 +105,7 @@ export function assertType(condition: unknown, type?: string): asserts condition * * @see {@link assertDefined} for a similar utility that leverages TS assertion functions to narrow down the type of `arg` to be non-nullable. */ -export function assertIsDefined(arg: T | null | undefined): NonNullable { +export function assertReturnsDefined(arg: T | null | undefined): NonNullable { assert( arg !== null && arg !== undefined, 'Argument is `undefined` or `null`.', @@ -138,7 +137,7 @@ export function assertIsDefined(arg: T | null | undefined): NonNullable { * console.log(someValue.length); // now type of `someValue` is `string` * ``` * - * @see {@link assertIsDefined} for a similar utility but without assertion. + * @see {@link assertReturnsDefined} for a similar utility but without assertion. * @see {@link https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions typescript-3-7.html#assertion-functions} */ export function assertDefined(value: T, error: string | NonNullable): asserts value is NonNullable { @@ -152,10 +151,10 @@ export function assertDefined(value: T, error: string | NonNullable): /** * Asserts that each argument passed in is neither undefined nor null. */ -export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2]; -export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3]; -export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4]; -export function assertAllDefined(...args: (unknown | null | undefined)[]): unknown[] { +export function assertReturnsAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2]; +export function assertReturnsAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3]; +export function assertReturnsAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4]; +export function assertReturnsAllDefined(...args: (unknown | null | undefined)[]): unknown[] { const result = []; for (let i = 0; i < args.length; i++) { @@ -342,8 +341,3 @@ export type DeepPartial = { * Represents a type that is a partial version of a given type `T`, except a subset. */ export type PartialExcept = Partial> & Pick; - -/** - * Type for an `object` with its `value` property being a {@link URI}. - */ -export type WithUriValue = T & { value: URI }; diff --git a/src/vs/base/common/worker/webWorkerBootstrap.ts b/src/vs/base/common/worker/webWorkerBootstrap.ts index dce9e79b757..946c3e98be9 100644 --- a/src/vs/base/common/worker/webWorkerBootstrap.ts +++ b/src/vs/base/common/worker/webWorkerBootstrap.ts @@ -6,7 +6,7 @@ import { IWebWorkerServerRequestHandler, IWebWorkerServerRequestHandlerFactory, WebWorkerServer } from './webWorker.js'; type MessageEvent = { - data: any; + data: unknown; }; declare const globalThis: { diff --git a/src/vs/base/node/extpath.ts b/src/vs/base/node/extpath.ts deleted file mode 100644 index 60fbdd6e4c7..00000000000 --- a/src/vs/base/node/extpath.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import { CancellationToken } from '../common/cancellation.js'; -import { basename, dirname, join, normalize, sep } from '../common/path.js'; -import { isLinux } from '../common/platform.js'; -import { rtrim } from '../common/strings.js'; -import { Promises } from './pfs.js'; - -/** - * Copied from: https://github.com/microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83 - * - * Given an absolute, normalized, and existing file path 'realcase' returns the exact path that the file has on disk. - * On a case insensitive file system, the returned path might differ from the original path by character casing. - * On a case sensitive file system, the returned path will always be identical to the original path. - * In case of errors, null is returned. But you cannot use this function to verify that a path exists. - * realcase does not handle '..' or '.' path segments and it does not take the locale into account. - */ -export async function realcase(path: string, token?: CancellationToken): Promise { - if (isLinux) { - // This method is unsupported on OS that have case sensitive - // file system where the same path can exist in different forms - // (see also https://github.com/microsoft/vscode/issues/139709) - return path; - } - - const dir = dirname(path); - if (path === dir) { // end recursion - return path; - } - - const name = (basename(path) /* can be '' for windows drive letters */ || path).toLowerCase(); - try { - if (token?.isCancellationRequested) { - return null; - } - - const entries = await Promises.readdir(dir); - const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search - if (found.length === 1) { - // on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition - const prefix = await realcase(dir, token); // recurse - if (prefix) { - return join(prefix, found[0]); - } - } else if (found.length > 1) { - // must be a case sensitive $filesystem - const ix = found.indexOf(name); - if (ix >= 0) { // case sensitive - const prefix = await realcase(dir, token); // recurse - if (prefix) { - return join(prefix, found[ix]); - } - } - } - } catch (error) { - // silently ignore error - } - - return null; -} - -export async function realpath(path: string): Promise { - try { - // DO NOT USE `fs.promises.realpath` here as it internally - // calls `fs.native.realpath` which will result in subst - // drives to be resolved to their target on Windows - // https://github.com/microsoft/vscode/issues/118562 - return await Promises.realpath(path); - } catch (error) { - - // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization - // we now do a similar normalization and then try again if we can access the path with read - // permissions at least. If that succeeds, we return that path. - // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is - // to not resolve links but to simply see if the path is read accessible or not. - const normalizedPath = normalizePath(path); - - await fs.promises.access(normalizedPath, fs.constants.R_OK); - - return normalizedPath; - } -} - -export function realpathSync(path: string): string { - try { - return fs.realpathSync(path); - } catch (error) { - - // We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization - // we now do a similar normalization and then try again if we can access the path with read - // permissions at least. If that succeeds, we return that path. - // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is - // to not resolve links but to simply see if the path is read accessible or not. - const normalizedPath = normalizePath(path); - - fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error - - return normalizedPath; - } -} - -function normalizePath(path: string): string { - return rtrim(normalize(path), sep); -} diff --git a/src/vs/base/node/nodeStreams.ts b/src/vs/base/node/nodeStreams.ts index 0dba130a07d..a7516e1e598 100644 --- a/src/vs/base/node/nodeStreams.ts +++ b/src/vs/base/node/nodeStreams.ts @@ -28,7 +28,7 @@ export class StreamSplitter extends Transform { } } - override _transform(chunk: Buffer, _encoding: string, callback: (error?: Error | null, data?: any) => void): void { + override _transform(chunk: Buffer, _encoding: string, callback: (error?: Error | null, data?: Buffer) => void): void { if (!this.buffer) { this.buffer = chunk; } else { @@ -52,7 +52,7 @@ export class StreamSplitter extends Transform { callback(); } - override _flush(callback: (error?: Error | null, data?: any) => void): void { + override _flush(callback: (error?: Error | null, data?: Buffer) => void): void { if (this.buffer) { this.push(this.buffer); } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 052ec7fa1c7..cf76e466400 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -9,10 +9,12 @@ import { promisify } from 'util'; import { ResourceQueue, timeout } from '../common/async.js'; import { isEqualOrParent, isRootOrDriveLetter, randomPath } from '../common/extpath.js'; import { normalizeNFC } from '../common/normalization.js'; -import { join } from '../common/path.js'; +import { basename, dirname, join, normalize, sep } from '../common/path.js'; import { isLinux, isMacintosh, isWindows } from '../common/platform.js'; import { extUriBiasedIgnorePathCase } from '../common/resources.js'; import { URI } from '../common/uri.js'; +import { CancellationToken } from '../common/cancellation.js'; +import { rtrim } from '../common/strings.js'; //#region rimraf @@ -82,14 +84,6 @@ async function rimrafUnlink(path: string): Promise { return fs.promises.rm(path, { recursive: true, force: true, maxRetries: 3 }); } -export function rimrafSync(path: string): void { - if (isRootOrDriveLetter(path)) { - throw new Error('rimraf - will refuse to recursively delete root'); - } - - fs.rmSync(path, { recursive: true, force: true, maxRetries: 3 }); -} - //#endregion //#region readdir with NFC support (macos) @@ -154,15 +148,6 @@ async function safeReaddirWithFileTypes(path: string): Promise { return result; } -/** - * Drop-in replacement of `fs.readdirSync` with support - * for converting from macOS NFD unicon form to NFC - * (https://github.com/nodejs/node/issues/2165) - */ -export function readdirSync(path: string): string[] { - return handleDirectoryChildren(fs.readdirSync(path)); -} - function handleDirectoryChildren(children: string[]): string[]; function handleDirectoryChildren(children: IDirent[]): IDirent[]; function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[]; @@ -437,6 +422,8 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o * Same as `fs.writeFileSync` but with an additional call to * `fs.fdatasyncSync` after writing to ensure changes are * flushed to disk. + * + * @deprecated always prefer async variants over sync! */ export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void { const ensuredOptions = ensureWriteOptions(options); @@ -657,6 +644,113 @@ async function doCopySymlink(source: string, target: string, payload: ICopyPaylo //#endregion +//#region Path resolvers + +/** + * Given an absolute, normalized, and existing file path 'realcase' returns the + * exact path that the file has on disk. + * On a case insensitive file system, the returned path might differ from the original + * path by character casing. + * On a case sensitive file system, the returned path will always be identical to the + * original path. + * In case of errors, null is returned. But you cannot use this function to verify that + * a path exists. + * + * realcase does not handle '..' or '.' path segments and it does not take the locale into account. + */ +export async function realcase(path: string, token?: CancellationToken): Promise { + if (isLinux) { + // This method is unsupported on OS that have case sensitive + // file system where the same path can exist in different forms + // (see also https://github.com/microsoft/vscode/issues/139709) + return path; + } + + const dir = dirname(path); + if (path === dir) { // end recursion + return path; + } + + const name = (basename(path) /* can be '' for windows drive letters */ || path).toLowerCase(); + try { + if (token?.isCancellationRequested) { + return null; + } + + const entries = await Promises.readdir(dir); + const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search + if (found.length === 1) { + // on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition + const prefix = await realcase(dir, token); // recurse + if (prefix) { + return join(prefix, found[0]); + } + } else if (found.length > 1) { + // must be a case sensitive $filesystem + const ix = found.indexOf(name); + if (ix >= 0) { // case sensitive + const prefix = await realcase(dir, token); // recurse + if (prefix) { + return join(prefix, found[ix]); + } + } + } + } catch (error) { + // silently ignore error + } + + return null; +} + +async function realpath(path: string): Promise { + try { + // DO NOT USE `fs.promises.realpath` here as it internally + // calls `fs.native.realpath` which will result in subst + // drives to be resolved to their target on Windows + // https://github.com/microsoft/vscode/issues/118562 + return await promisify(fs.realpath)(path); + } catch (error) { + + // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization + // we now do a similar normalization and then try again if we can access the path with read + // permissions at least. If that succeeds, we return that path. + // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is + // to not resolve links but to simply see if the path is read accessible or not. + const normalizedPath = normalizePath(path); + + await fs.promises.access(normalizedPath, fs.constants.R_OK); + + return normalizedPath; + } +} + +/** + * @deprecated always prefer async variants over sync! + */ +export function realpathSync(path: string): string { + try { + return fs.realpathSync(path); + } catch (error) { + + // We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization + // we now do a similar normalization and then try again if we can access the path with read + // permissions at least. If that succeeds, we return that path. + // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is + // to not resolve links but to simply see if the path is read accessible or not. + const normalizedPath = normalizePath(path); + + fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error + + return normalizedPath; + } +} + +function normalizePath(path: string): string { + return rtrim(normalize(path), sep); +} + +//#endregion + //#region Promise based fs methods /** @@ -711,14 +805,12 @@ export const Promises = new class { }; } - get fdatasync() { return promisify(fs.fdatasync); } // not exposed as API in 20.x yet + get fdatasync() { return promisify(fs.fdatasync); } // not exposed as API in 22.x yet get open() { return promisify(fs.open); } // changed to return `FileHandle` in promise API get close() { return promisify(fs.close); } // not exposed as API due to the `FileHandle` return type of `open` - get realpath() { return promisify(fs.realpath); } // `fs.promises.realpath` will use `fs.realpath.native` which we do not want - - get ftruncate() { return promisify(fs.ftruncate); } // not exposed as API in 20.x yet + get ftruncate() { return promisify(fs.ftruncate); } // not exposed as API in 22.x yet //#endregion @@ -744,6 +836,8 @@ export const Promises = new class { get rename() { return rename; } get copy() { return copy; } + get realpath() { return realpath; } // `fs.promises.realpath` will use `fs.realpath.native` which we do not want + //#endregion }; diff --git a/src/vs/base/node/ports.ts b/src/vs/base/node/ports.ts index b664a8a0f4e..a237f433ea0 100644 --- a/src/vs/base/node/ports.ts +++ b/src/vs/base/node/ports.ts @@ -64,7 +64,7 @@ function doFindFreePort(startPort: number, giveUpAfter: number, stride: number, } // Reference: https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc#56 -export const BROWSER_RESTRICTED_PORTS: any = { +export const BROWSER_RESTRICTED_PORTS: Record = { 1: true, // tcpmux 7: true, // echo 9: true, // discard diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 3a9fd987e6d..c2028fc5409 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -8,10 +8,12 @@ import { Stats, promises } from 'fs'; import { getCaseInsensitive } from '../common/objects.js'; import * as path from '../common/path.js'; import * as Platform from '../common/platform.js'; -import * as process from '../common/process.js'; +import * as processCommon from '../common/process.js'; import { CommandOptions, ForkOptions, Source, SuccessData, TerminateResponse, TerminateResponseCode } from '../common/processes.js'; import * as Types from '../common/types.js'; import * as pfs from './pfs.js'; +import { FileAccess } from '../common/network.js'; +import Stream from 'stream'; export { Source, TerminateResponseCode, type CommandOptions, type ForkOptions, type SuccessData, type TerminateResponse }; export type ValueCallback = (value: T | Promise) => void; @@ -19,7 +21,7 @@ export type ErrorCallback = (error?: any) => void; export type ProgressCallback = (progress: T) => void; -export function getWindowsShell(env = process.env as Platform.IProcessEnvironment): string { +export function getWindowsShell(env = processCommon.env as Platform.IProcessEnvironment): string { return env['comspec'] || 'cmd.exe'; } @@ -81,17 +83,17 @@ async function fileExistsDefault(path: string): Promise { return false; } -export function getWindowPathExtensions(env = process.env) { +export function getWindowPathExtensions(env = processCommon.env) { return (getCaseInsensitive(env, 'PATHEXT') as string || '.COM;.EXE;.BAT;.CMD').split(';'); } -export async function findExecutable(command: string, cwd?: string, paths?: string[], env: Platform.IProcessEnvironment = process.env as Platform.IProcessEnvironment, fileExists: (path: string) => Promise = fileExistsDefault): Promise { +export async function findExecutable(command: string, cwd?: string, paths?: string[], env: Platform.IProcessEnvironment = processCommon.env as Platform.IProcessEnvironment, fileExists: (path: string) => Promise = fileExistsDefault): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return await fileExists(command) ? command : undefined; } if (cwd === undefined) { - cwd = process.cwd(); + cwd = processCommon.cwd(); } const dir = path.dirname(command); if (dir !== '.') { @@ -140,3 +142,42 @@ export async function findExecutable(command: string, cwd?: string, paths?: stri const fullPath = path.join(cwd, command); return await fileExists(fullPath) ? fullPath : undefined; } + +/** + * Kills a process and all its children. + * @param pid the process id to kill + * @param forceful whether to forcefully kill the process (default: false). Note + * that on Windows, terminal processes can _only_ be killed forcefully and this + * will throw when not forceful. + */ +export async function killTree(pid: number, forceful = false) { + let child: cp.ChildProcessByStdio; + if (Platform.isWindows) { + const windir = process.env['WINDIR'] || 'C:\\Windows'; + const taskKill = path.join(windir, 'System32', 'taskkill.exe'); + + const args = ['/T']; + if (forceful) { + args.push('/F'); + } + args.push('/PID', String(pid)); + child = cp.spawn(taskKill, args, { stdio: ['ignore', 'pipe', 'pipe'] }); + } else { + const killScript = FileAccess.asFileUri('vs/base/node/terminateProcess.sh').fsPath; + child = cp.spawn('/bin/sh', [killScript, String(pid), forceful ? '9' : '15'], { stdio: ['ignore', 'pipe', 'pipe'] }); + } + + return new Promise((resolve, reject) => { + const stdout: Buffer[] = []; + child.stdout.on('data', (data) => stdout.push(data)); + child.stderr.on('data', (data) => stdout.push(data)); + child.on('error', reject); + child.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`taskkill exited with code ${code}: ${Buffer.concat(stdout).toString()}`)); + } + }); + }); +} diff --git a/src/vs/base/node/terminateProcess.sh b/src/vs/base/node/terminateProcess.sh index acdcbf8ed42..a8e8738fdb4 100755 --- a/src/vs/base/node/terminateProcess.sh +++ b/src/vs/base/node/terminateProcess.sh @@ -1,12 +1,13 @@ -#!/bin/bash +#!/bin/sh + +ROOT_PID=$1 +SIGNAL=$2 terminateTree() { - for cpid in $(/usr/bin/pgrep -P $1); do - terminateTree $cpid - done - kill -9 $1 > /dev/null 2>&1 + for cpid in $(pgrep -P $1); do + terminateTree $cpid + done + kill -$SIGNAL $1 > /dev/null 2>&1 } -for pid in $*; do - terminateTree $pid -done \ No newline at end of file +terminateTree $ROOT_PID diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index 8de90376c70..090db2da3f0 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -8,7 +8,7 @@ import { Readable } from 'stream'; import { createCancelablePromise, Sequencer } from '../common/async.js'; import { CancellationToken } from '../common/cancellation.js'; import * as path from '../common/path.js'; -import { assertIsDefined } from '../common/types.js'; +import { assertReturnsDefined } from '../common/types.js'; import { Promises } from './pfs.js'; import * as nls from '../../nls.js'; import type { Entry, ZipFile } from 'yauzl'; @@ -168,7 +168,7 @@ async function openZip(zipFile: string, lazy: boolean = false): Promise if (error) { reject(toExtractError(error)); } else { - resolve(assertIsDefined(zipfile)); + resolve(assertReturnsDefined(zipfile)); } }); }); @@ -180,7 +180,7 @@ function openZipStream(zipFile: ZipFile, entry: Entry): Promise { if (error) { reject(toExtractError(error)); } else { - resolve(assertIsDefined(stream)); + resolve(assertReturnsDefined(stream)); } }); }); diff --git a/src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts similarity index 97% rename from src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts rename to src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts index 845cf33c343..3eab2e24b9d 100644 --- a/src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CONTEXT_MENU_CHANNEL, CONTEXT_MENU_CLOSE_CHANNEL, IContextMenuEvent, IContextMenuItem, IPopupOptions, ISerializableContextMenuItem } from '../common/contextmenu.js'; -import { ipcRenderer } from '../../sandbox/electron-sandbox/globals.js'; +import { ipcRenderer } from '../../sandbox/electron-browser/globals.js'; let contextMenuIdPool = 0; diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 7ab922e8b82..5e0764d9849 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -260,9 +260,6 @@ const BufferPresets = { Uint: createOneByteBuffer(DataType.Int), }; -declare const Buffer: any; -const hasBuffer = (typeof Buffer !== 'undefined'); - export function serialize(writer: IWriter, data: any): void { if (typeof data === 'undefined') { writer.write(BufferPresets.Undefined); @@ -271,7 +268,7 @@ export function serialize(writer: IWriter, data: any): void { writer.write(BufferPresets.String); writeInt32VQL(writer, buffer.byteLength); writer.write(buffer); - } else if (hasBuffer && Buffer.isBuffer(data)) { + } else if (VSBuffer.isNativeBuffer(data)) { const buffer = VSBuffer.wrap(data); writer.write(BufferPresets.Buffer); writeInt32VQL(writer, buffer.byteLength); diff --git a/src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts b/src/vs/base/parts/ipc/electron-browser/ipc.electron.ts similarity index 95% rename from src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts rename to src/vs/base/parts/ipc/electron-browser/ipc.electron.ts index 553a310e978..4a0ae19a37d 100644 --- a/src/vs/base/parts/ipc/electron-sandbox/ipc.electron.ts +++ b/src/vs/base/parts/ipc/electron-browser/ipc.electron.ts @@ -8,7 +8,7 @@ import { Event } from '../../../common/event.js'; import { IDisposable } from '../../../common/lifecycle.js'; import { IPCClient } from '../common/ipc.js'; import { Protocol as ElectronProtocol } from '../common/ipc.electron.js'; -import { ipcRenderer } from '../../sandbox/electron-sandbox/globals.js'; +import { ipcRenderer } from '../../sandbox/electron-browser/globals.js'; /** * An implementation of `IPCClient` on top of Electron `ipcRenderer` IPC communication diff --git a/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts b/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts similarity index 98% rename from src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts rename to src/vs/base/parts/ipc/electron-browser/ipc.mp.ts index 4a36b238817..407ff931794 100644 --- a/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts +++ b/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts @@ -6,7 +6,7 @@ import { mainWindow } from '../../../browser/window.js'; import { Event } from '../../../common/event.js'; import { generateUuid } from '../../../common/uuid.js'; -import { ipcMessagePort, ipcRenderer } from '../../sandbox/electron-sandbox/globals.js'; +import { ipcMessagePort, ipcRenderer } from '../../sandbox/electron-browser/globals.js'; interface IMessageChannelResult { nonce: string; diff --git a/src/vs/base/parts/ipc/electron-main/ipc.mp.ts b/src/vs/base/parts/ipc/electron-main/ipc.mp.ts index dacaac11a56..4f2dc1987b3 100644 --- a/src/vs/base/parts/ipc/electron-main/ipc.mp.ts +++ b/src/vs/base/parts/ipc/electron-main/ipc.mp.ts @@ -34,7 +34,7 @@ export class Client extends MessagePortClient implements IDisposable { /** * This method opens a message channel connection * in the target window. The target window needs - * to use the `Server` from `electron-sandbox/ipc.mp`. + * to use the `Server` from `electron-browser/ipc.mp`. */ export async function connect(window: BrowserWindow): Promise { diff --git a/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts b/src/vs/base/parts/ipc/test/electron-browser/ipc.mp.test.ts similarity index 100% rename from src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts rename to src/vs/base/parts/ipc/test/electron-browser/ipc.mp.test.ts diff --git a/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts b/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts similarity index 99% rename from src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts rename to src/vs/base/parts/sandbox/electron-browser/electronTypes.ts index a132d7d6eb4..cbf98ff13c5 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts +++ b/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts @@ -6,7 +6,7 @@ // ####################################################################### // ### ### -// ### electron.d.ts types we expose from electron-sandbox ### +// ### electron.d.ts types we expose from electron-browser ### // ### (copied from Electron 29.x) ### // ### ### // ####################################################################### diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-browser/globals.ts similarity index 100% rename from src/vs/base/parts/sandbox/electron-sandbox/globals.ts rename to src/vs/base/parts/sandbox/electron-browser/globals.ts diff --git a/src/vs/base/parts/sandbox/electron-sandbox/preload-aux.ts b/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts similarity index 100% rename from src/vs/base/parts/sandbox/electron-sandbox/preload-aux.ts rename to src/vs/base/parts/sandbox/electron-browser/preload-aux.ts diff --git a/src/vs/base/parts/sandbox/electron-sandbox/preload.ts b/src/vs/base/parts/sandbox/electron-browser/preload.ts similarity index 100% rename from src/vs/base/parts/sandbox/electron-sandbox/preload.ts rename to src/vs/base/parts/sandbox/electron-browser/preload.ts diff --git a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts b/src/vs/base/parts/sandbox/test/electron-browser/globals.test.ts similarity index 96% rename from src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts rename to src/vs/base/parts/sandbox/test/electron-browser/globals.test.ts index d979b221111..dcc6b0abc18 100644 --- a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts +++ b/src/vs/base/parts/sandbox/test/electron-browser/globals.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { ipcRenderer, process, webFrame, webUtils } from '../../electron-sandbox/globals.js'; +import { ipcRenderer, process, webFrame, webUtils } from '../../electron-browser/globals.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../test/common/utils.js'; suite('Sandbox', () => { diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index b9a18cffdf2..89d4ab02a1d 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -6,7 +6,6 @@ import assert from 'assert'; import * as arrays from '../../common/arrays.js'; import * as arraysFind from '../../common/arraysFind.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; -import { pick } from '../../common/arrays.js'; suite('Arrays', () => { @@ -400,63 +399,7 @@ suite('Arrays', () => { ); }); - suite('pick', () => { - suite('object', () => { - test('numbers', () => { - const array = [{ v: 3, foo: 'a' }, { v: 5, foo: 'b' }, { v: 2, foo: 'c' }, { v: 2, foo: 'd' }, { v: 17, bar: '1' }, { v: -100, baz: '10' }]; - assert.deepStrictEqual( - array.map(pick('v')), - [3, 5, 2, 2, 17, -100], - ); - }); - - test('strings', () => { - const array = [{ v: 3, foo: 'a' }, { v: 5, foo: 'b' }, { v: 2, foo: 'c' }, { v: 2, foo: 'd' }, { v: 17, bar: '1' }, { v: -100, baz: '10' }, { foo: '12' }]; - - assert.deepStrictEqual( - array.map(pick('foo')), - ['a', 'b', 'c', 'd', undefined, undefined, '12'], - ); - }); - - test('booleans', () => { - const array = [{ v: 3, foo: 'a' }, { v: 5, foo: 'b' }, { v: 2, foo: 'c' }, { v: 2, foo: 'd' }, { v: 17, bar: true }, { v: -100, bar: false }, { bar: false }]; - - assert.deepStrictEqual( - array.map(pick('bar')), - [undefined, undefined, undefined, undefined, true, false, false], - ); - }); - - test('objects', () => { - const array = [{ v: { test: 12 } }, { v: { test: 24 } }, {}, { v: { test: 17892 } }]; - - assert.deepStrictEqual( - array.map(pick('v')), - [{ test: 12 }, { test: 24 }, undefined, { test: 17892 }], - ); - }); - - test('mixed', () => { - const array = [{ v: { test: 104 } }, { v: 2 }, {}, { v: '24' }, { v: null }]; - - assert.deepStrictEqual( - array.map(pick('v')), - [{ test: 104 }, 2, undefined, '24', null], - ); - }); - }); - - test('string', () => { - const array = ['haallo', 'there', ':wave:', '!']; - - assert.deepStrictEqual( - array.map(pick('length')), - [6, 5, 6, 1], - ); - }); - }); suite('ArrayQueue', () => { suite('takeWhile/takeFromEndWhile', () => { diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index 9e3feeb4d34..dd91ac24b97 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -60,7 +60,7 @@ suite('Labels', () => { assert.deepStrictEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']), ['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']); assert.deepStrictEqual(labels.shorten(['a', 'a\\b', 'b']), ['a', 'a\\b', 'b']); assert.deepStrictEqual(labels.shorten(['', 'a', 'b', 'b\\c', 'a\\c']), ['.\\', 'a', 'b', 'b\\c', 'a\\c']); - assert.deepStrictEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-sandbox', 'src\\vs\\workbench\\parts\\execution\\electron-sandbox\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-sandbox']), ['…\\execution\\electron-sandbox', '…\\something', '…\\terminal\\…']); + assert.deepStrictEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-browser', 'src\\vs\\workbench\\parts\\execution\\electron-browser\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-browser']), ['…\\execution\\electron-browser', '…\\something', '…\\terminal\\…']); }); (isWindows ? test.skip : test)('shorten - not windows', () => { diff --git a/src/vs/base/test/common/logTime.test.ts b/src/vs/base/test/common/logTime.test.ts deleted file mode 100644 index 2e2f71127d9..00000000000 --- a/src/vs/base/test/common/logTime.test.ts +++ /dev/null @@ -1,274 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import * as sinon from 'sinon'; -import { waitRandom } from './testUtils.js'; -import { randomInt } from '../../common/numbers.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; -import { logExecutionTime, logTime } from '../../common/decorators/logTime.js'; - -suite('logTime', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - /** - * Helper that replaces the timing part of a message to - * a predictable constant value so the message can be - * consistently compared in the tests - */ - const cleanupTimingMessage = ( - message: string, - ): string => { - // sanity check on the message type since this function - // can be called with `any` inside these tests - assert( - typeof message === 'string', - `Message must be a string, got '${message}'.`, - ); - - // regex: targets the ' 123.75 ms' part at the end - // of the provided 'message' string - return message - .replaceAll(/\s\d+.\d{2}\sms$/gi, ' 100.50 ms'); - }; - - suite('• decorator', () => { - test('• async method', async () => { - const logSpy = sinon.spy(); - class TestClass { - public logTime = logSpy; - - constructor( - private readonly returnValue: number - ) { } - - @logTime() - public async myAsyncMethod(): Promise { - await waitRandom(10); - - return this.returnValue; - } - } - - const expectedReturnValue = randomInt(1000); - const testObject = new TestClass(expectedReturnValue); - - const resultPromise = testObject.myAsyncMethod(); - - assert( - resultPromise instanceof Promise, - 'My method must return a promise.', - ); - - const result = await resultPromise; - assert.strictEqual( - result, - expectedReturnValue, - 'Decorator must return correct value.', - ); - - assert( - logSpy.calledOnce, - 'The trace logger method must be called.', - ); - - const callArgs = logSpy.getCalls()[0].args; - - assert( - callArgs.length === 1, - 'Logger method must be called with correct number of arguments.', - ); - - assert.strictEqual( - cleanupTimingMessage(callArgs[0]), - '[⏱][TestClass.myAsyncMethod] took 100.50 ms', - 'Logger method must be called with correct message.', - ); - }); - }); - - test('• sync method', async () => { - const logSpy = sinon.spy(); - - class TestClass { - public logTime = logSpy; - - constructor( - private readonly returnValue: number - ) { } - - @logTime() - public mySyncMethod(): number { - return this.returnValue; - } - } - - const expectedReturnValue = randomInt(1000); - const testObject = new TestClass(expectedReturnValue); - - const result = testObject.mySyncMethod(); - assert.strictEqual( - result, - expectedReturnValue, - 'Decorator must return correct value.', - ); - - assert( - logSpy.calledOnce, - 'The trace logger method must be called.', - ); - - const callArgs = logSpy.getCalls()[0].args; - - assert( - callArgs.length === 1, - 'Logger method must be called with correct number of arguments.', - ); - - assert.strictEqual( - cleanupTimingMessage(callArgs[0]), - '[⏱][TestClass.mySyncMethod] took 100.50 ms', - 'Logger method must be called with correct message.', - ); - }); - - test('• uses \'trace\' level by default', async () => { - const logSpy = sinon.spy(); - - class TestClass { - public logTime = logSpy; - - constructor( - private readonly returnValue: number - ) { } - - @logTime() - public async myAsyncMethod(): Promise { - await waitRandom(10); - - return this.returnValue; - } - } - - const expectedReturnValue = randomInt(1000); - const testObject = new TestClass(expectedReturnValue); - - const resultPromise = testObject.myAsyncMethod(); - - assert( - resultPromise instanceof Promise, - 'My method must return a promise.', - ); - - const result = await resultPromise; - assert.strictEqual( - result, - expectedReturnValue, - 'Decorator must return correct value.', - ); - - assert( - logSpy.calledOnce, - 'The trace logger method must be called.', - ); - - const callArgs = logSpy.getCalls()[0].args; - - assert( - callArgs.length === 1, - 'Logger method must be called with correct number of arguments.', - ); - - assert.strictEqual( - cleanupTimingMessage(callArgs[0]), - '[⏱][TestClass.myAsyncMethod] took 100.50 ms', - 'Logger method must be called with correct message.', - ); - }); - - suite('• logExecutionTime helper', () => { - test('• async function', async () => { - const logSpy = sinon.spy(); - - const expectedReturnValue = randomInt(1000); - const resultPromise = logExecutionTime( - 'my-async-function', - async () => { - await waitRandom(10); - - return expectedReturnValue; - }, - logSpy, - ); - - assert( - resultPromise instanceof Promise, - 'Callback function must return a promise.', - ); - - const result = await resultPromise; - assert.strictEqual( - result, - expectedReturnValue, - 'Helper must return correct value.', - ); - - assert( - logSpy.calledOnce, - 'The trace logger method must be called.', - ); - - const callArgs = logSpy.getCalls()[0].args; - - assert( - callArgs.length === 1, - 'Logger method must be called with correct number of arguments.', - ); - - assert.strictEqual( - cleanupTimingMessage(callArgs[0]), - '[⏱][my-async-function] took 100.50 ms', - 'Logger message must start with the correct value.', - ); - }); - - test('• sync function', () => { - const logSpy = sinon.spy(); - - const expectedReturnValue = randomInt(1000); - const result = logExecutionTime( - 'my-sync-function', - () => { - return expectedReturnValue; - }, - logSpy, - ); - - assert.strictEqual( - result, - expectedReturnValue, - 'Helper must return correct value.', - ); - - assert( - logSpy.calledOnce, - 'The trace logger method must be called.', - ); - - const callArgs = logSpy.getCalls()[0].args; - - assert( - callArgs.length === 1, - 'Logger method must be called with correct number of arguments.', - ); - - assert.strictEqual( - cleanupTimingMessage(callArgs[0]), - '[⏱][my-sync-function] took 100.50 ms', - 'Logger message must start with the correct value.', - ); - }); - }); -}); diff --git a/src/vs/base/test/common/mockObject.test.ts b/src/vs/base/test/common/mockObject.test.ts deleted file mode 100644 index eff91bb91a4..00000000000 --- a/src/vs/base/test/common/mockObject.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { typeCheck } from '../../common/types.js'; -import { randomInt } from '../../common/numbers.js'; -import { mockObject, randomBoolean } from './testUtils.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; - - -suite('mockObject', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('• overrides properties and functions', () => { - interface ITestObject { - foo: string; - bar: string; - readonly baz: number; - someMethod(arg: boolean): string; - anotherMethod(arg: number): boolean; - } - - const mock = mockObject({ - bar: 'oh hi!', - baz: 42, - anotherMethod(arg: number): boolean { - return isNaN(arg); - }, - }); - - typeCheck(mock); - - assert.strictEqual( - mock.bar, - 'oh hi!', - 'bar should be overridden', - ); - - assert.strictEqual( - mock.baz, - 42, - 'baz should be overridden', - ); - - assert( - !(mock.anotherMethod(randomInt(100))), - 'Must execute overridden method correctly 1.', - ); - - assert( - mock.anotherMethod(NaN), - 'Must execute overridden method correctly 2.', - ); - - assert.throws(() => { - // property is not overridden so must throw - // eslint-disable-next-line local/code-no-unused-expressions - mock.foo; - }); - - assert.throws(() => { - // function is not overridden so must throw - mock.someMethod(randomBoolean()); - }); - }); - - test('• immutability of the overrides object', () => { - interface ITestObject { - foo: string; - bar: string; - readonly baz: number; - someMethod(arg: boolean): string; - anotherMethod(arg: number): boolean; - } - - const overrides: Partial = { - baz: 4, - }; - const mock = mockObject(overrides); - typeCheck(mock); - - assert.strictEqual( - mock.baz, - 4, - 'baz should be overridden', - ); - - // overrides object must be immutable - assert.throws(() => { - overrides.foo = 'test'; - }); - - assert.throws(() => { - overrides.someMethod = (arg: boolean) => { - return `${arg}__${arg}`; - }; - }); - }); -}); diff --git a/src/vs/base/test/common/oauth.test.ts b/src/vs/base/test/common/oauth.test.ts index 2dbeda2f1fe..0ef67b6dfe4 100644 --- a/src/vs/base/test/common/oauth.test.ts +++ b/src/vs/base/test/common/oauth.test.ts @@ -11,7 +11,7 @@ import { getMetadataWithDefaultValues, isAuthorizationAuthorizeResponse, isAuthorizationDeviceResponse, - isAuthorizationDeviceTokenErrorResponse, + isAuthorizationErrorResponse, isAuthorizationDynamicClientRegistrationResponse, isAuthorizationProtectedResourceMetadata, isAuthorizationServerMetadata, @@ -138,43 +138,42 @@ suite('OAuth', () => { assert.strictEqual(isAuthorizationDeviceResponse('not an object'), false); }); - test('isAuthorizationDeviceTokenErrorResponse should correctly identify device token error response', () => { + test('isAuthorizationErrorResponse should correctly identify error response', () => { // Valid error response - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ + assert.strictEqual(isAuthorizationErrorResponse({ error: 'authorization_pending', error_description: 'The authorization request is still pending' }), true); // Valid error response with different error codes - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ + assert.strictEqual(isAuthorizationErrorResponse({ error: 'slow_down', error_description: 'Polling too fast' }), true); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ + assert.strictEqual(isAuthorizationErrorResponse({ error: 'access_denied', error_description: 'The user denied the request' }), true); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ + assert.strictEqual(isAuthorizationErrorResponse({ error: 'expired_token', error_description: 'The device code has expired' }), true); // Valid response with optional error_uri - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ + assert.strictEqual(isAuthorizationErrorResponse({ error: 'invalid_request', error_description: 'The request is missing a required parameter', error_uri: 'https://example.com/error' }), true); // Invalid cases - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse(null), false); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse(undefined), false); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({}), false); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ error: 'missing-description' }), false); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse({ error_description: 'missing-error' }), false); - assert.strictEqual(isAuthorizationDeviceTokenErrorResponse('not an object'), false); + assert.strictEqual(isAuthorizationErrorResponse(null), false); + assert.strictEqual(isAuthorizationErrorResponse(undefined), false); + assert.strictEqual(isAuthorizationErrorResponse({}), false); + assert.strictEqual(isAuthorizationErrorResponse({ error_description: 'missing-error' }), false); + assert.strictEqual(isAuthorizationErrorResponse('not an object'), false); }); }); diff --git a/src/vs/base/test/common/testUtils.ts b/src/vs/base/test/common/testUtils.ts index cc80cf68514..91011c41299 100644 --- a/src/vs/base/test/common/testUtils.ts +++ b/src/vs/base/test/common/testUtils.ts @@ -3,9 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assert } from '../../common/assert.js'; -import { isOneOf } from '../../common/types.js'; -import { randomInt } from '../../common/numbers.js'; export function flakySuite(title: string, fn: () => void) /* Suite */ { return suite(title, function () { @@ -22,22 +19,6 @@ export function flakySuite(title: string, fn: () => void) /* Suite */ { }); } -/** - * @deprecated use `async#timeout` instead - */ -export const wait = (ms: number): Promise => { - return new Promise(resolve => setTimeout(resolve, ms)); -}; - -/** - * Helper function that allows to await for a random amount of time. - * @param maxMs The `maximum` amount of time to wait, in milliseconds. - * @param minMs [`optional`] The `minimum` amount of time to wait, in milliseconds. - */ -export const waitRandom = (maxMs: number, minMs: number = 0): Promise => { - return wait(randomInt(maxMs, minMs)); -}; - /** * (pseudo)Random boolean generator. * @@ -51,32 +32,3 @@ export const waitRandom = (maxMs: number, minMs: number = 0): Promise => { export const randomBoolean = (): boolean => { return Math.random() > 0.5; }; - -/** - * @deprecated use `mock.ts#mock` instead - */ -export function mockObject( - overrides: Partial, -): TObject { - // ensure that the overrides object cannot be modified afterward - overrides = Object.freeze(overrides); - - const keys = Object.keys(overrides) as (keyof (typeof overrides))[]; - const service = new Proxy( - {}, - { - get: (_target, key: string | number | Symbol) => { - // sanity check for the provided `key` - assert( - isOneOf(key, keys), - `The '${key}' is not mocked.`, - ); - - return overrides[key]; - }, - }); - - // note! it's ok to `as TObject` here, because of - // the runtime checks in the `Proxy` getter - return service as (typeof overrides) as TObject; -} diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 9b4aa432467..7195c458a57 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -158,18 +158,18 @@ suite('Types', () => { }); test('assertIsDefined / assertAreDefined', () => { - assert.throws(() => types.assertIsDefined(undefined)); - assert.throws(() => types.assertIsDefined(null)); - assert.throws(() => types.assertAllDefined(null, undefined)); - assert.throws(() => types.assertAllDefined(true, undefined)); - assert.throws(() => types.assertAllDefined(undefined, false)); + assert.throws(() => types.assertReturnsDefined(undefined)); + assert.throws(() => types.assertReturnsDefined(null)); + assert.throws(() => types.assertReturnsAllDefined(null, undefined)); + assert.throws(() => types.assertReturnsAllDefined(true, undefined)); + assert.throws(() => types.assertReturnsAllDefined(undefined, false)); - assert.strictEqual(types.assertIsDefined(true), true); - assert.strictEqual(types.assertIsDefined(false), false); - assert.strictEqual(types.assertIsDefined('Hello'), 'Hello'); - assert.strictEqual(types.assertIsDefined(''), ''); + assert.strictEqual(types.assertReturnsDefined(true), true); + assert.strictEqual(types.assertReturnsDefined(false), false); + assert.strictEqual(types.assertReturnsDefined('Hello'), 'Hello'); + assert.strictEqual(types.assertReturnsDefined(''), ''); - const res = types.assertAllDefined(1, true, 'Hello'); + const res = types.assertReturnsAllDefined(1, true, 'Hello'); assert.strictEqual(res[0], 1); assert.strictEqual(res[1], true); assert.strictEqual(res[2], 'Hello'); diff --git a/src/vs/base/test/node/extpath.test.ts b/src/vs/base/test/node/extpath.test.ts deleted file mode 100644 index c86f3cea0d1..00000000000 --- a/src/vs/base/test/node/extpath.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import assert from 'assert'; -import { tmpdir } from 'os'; -import { realcase, realpath, realpathSync } from '../../node/extpath.js'; -import { Promises } from '../../node/pfs.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js'; -import { flakySuite, getRandomTestPath } from './testUtils.js'; - -flakySuite('Extpath', () => { - let testDir: string; - - setup(() => { - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'extpath'); - - return fs.promises.mkdir(testDir, { recursive: true }); - }); - - teardown(() => { - return Promises.rm(testDir); - }); - - test('realcase', async () => { - - // assume case insensitive file system - if (process.platform === 'win32' || process.platform === 'darwin') { - const upper = testDir.toUpperCase(); - const real = await realcase(upper); - - if (real) { // can be null in case of permission errors - assert.notStrictEqual(real, upper); - assert.strictEqual(real.toUpperCase(), upper); - assert.strictEqual(real, testDir); - } - } - - // linux, unix, etc. -> assume case sensitive file system - else { - let real = await realcase(testDir); - assert.strictEqual(real, testDir); - - real = await realcase(testDir.toUpperCase()); - assert.strictEqual(real, testDir.toUpperCase()); - } - }); - - test('realpath', async () => { - const realpathVal = await realpath(testDir); - assert.ok(realpathVal); - }); - - test('realpathSync', () => { - const realpath = realpathSync(testDir); - assert.ok(realpath); - }); - - ensureNoDisposablesAreLeakedInTestSuite(); -}); diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 1cc0b17f24d..3574bef988d 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -12,7 +12,7 @@ import { randomPath } from '../../../common/extpath.js'; import { FileAccess } from '../../../common/network.js'; import { basename, dirname, join, sep } from '../../../common/path.js'; import { isWindows } from '../../../common/platform.js'; -import { configureFlushOnWrite, Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from '../../../node/pfs.js'; +import { configureFlushOnWrite, Promises, realcase, realpathSync, RimRafMode, SymlinkSupport, writeFileSync } from '../../../node/pfs.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../common/utils.js'; import { flakySuite, getRandomTestPath } from '../testUtils.js'; @@ -146,34 +146,6 @@ flakySuite('PFS', function () { assert.ok(!fs.existsSync(testDir)); }); - test('rimrafSync - swallows file not found error', function () { - const nonExistingDir = join(testDir, 'not-existing'); - rimrafSync(nonExistingDir); - - assert.ok(!fs.existsSync(nonExistingDir)); - }); - - test('rimrafSync - simple', async () => { - fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - - rimrafSync(testDir); - - assert.ok(!fs.existsSync(testDir)); - }); - - test('rimrafSync - recursive folder structure', async () => { - fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); - fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); - - fs.mkdirSync(join(testDir, 'somefolder')); - fs.writeFileSync(join(testDir, 'somefolder', 'somefile.txt'), 'Contents'); - - rimrafSync(testDir); - - assert.ok(!fs.existsSync(testDir)); - }); - test('copy, rename and delete', async () => { const sourceDir = FileAccess.asFileUri('vs/base/test/node/pfs/fixtures').fsPath; const parentDir = join(tmpdir(), 'vsctests', 'pfs'); @@ -490,5 +462,39 @@ flakySuite('PFS', function () { assert.strictEqual(fs.readFileSync(testFile).toString(), largeString); }); + test('realcase', async () => { + + // assume case insensitive file system + if (process.platform === 'win32' || process.platform === 'darwin') { + const upper = testDir.toUpperCase(); + const real = await realcase(upper); + + if (real) { // can be null in case of permission errors + assert.notStrictEqual(real, upper); + assert.strictEqual(real.toUpperCase(), upper); + assert.strictEqual(real, testDir); + } + } + + // linux, unix, etc. -> assume case sensitive file system + else { + let real = await realcase(testDir); + assert.strictEqual(real, testDir); + + real = await realcase(testDir.toUpperCase()); + assert.strictEqual(real, testDir.toUpperCase()); + } + }); + + test('realpath', async () => { + const realpathVal = await Promises.realpath(testDir); + assert.ok(realpathVal); + }); + + test('realpathSync', () => { + const realpath = realpathSync(testDir); + assert.ok(realpath); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 3928342a049..f9ee35ab370 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isStandalone } from '../../../base/browser/browser.js'; +import { addDisposableListener } from '../../../base/browser/dom.js'; import { mainWindow } from '../../../base/browser/window.js'; import { VSBuffer, decodeBase64, encodeBase64 } from '../../../base/common/buffer.js'; import { Emitter } from '../../../base/common/event.js'; @@ -340,9 +341,7 @@ class LocalStorageURLCallbackProvider extends Disposable implements IURLCallback return; } - const fn = () => this.onDidChangeLocalStorage(); - mainWindow.addEventListener('storage', fn); - this.onDidChangeLocalStorageDisposable = { dispose: () => mainWindow.removeEventListener('storage', fn) }; + this.onDidChangeLocalStorageDisposable = addDisposableListener(mainWindow, 'storage', () => this.onDidChangeLocalStorage()); } private stopListening(): void { diff --git a/src/vs/code/electron-sandbox/workbench/workbench-dev.html b/src/vs/code/electron-browser/workbench/workbench-dev.html similarity index 100% rename from src/vs/code/electron-sandbox/workbench/workbench-dev.html rename to src/vs/code/electron-browser/workbench/workbench-dev.html diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html similarity index 100% rename from src/vs/code/electron-sandbox/workbench/workbench.html rename to src/vs/code/electron-browser/workbench/workbench.html diff --git a/src/vs/code/electron-sandbox/workbench/workbench.ts b/src/vs/code/electron-browser/workbench/workbench.ts similarity index 98% rename from src/vs/code/electron-sandbox/workbench/workbench.ts rename to src/vs/code/electron-browser/workbench/workbench.ts index 8c9ff16ec64..a49f0b2f278 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.ts +++ b/src/vs/code/electron-browser/workbench/workbench.ts @@ -11,11 +11,11 @@ performance.mark('code/didStartRenderer'); type ISandboxConfiguration = import('../../../base/parts/sandbox/common/sandboxTypes.js').ISandboxConfiguration; - type ILoadResult = import('../../../platform/window/electron-sandbox/window.js').ILoadResult; - type ILoadOptions = import('../../../platform/window/electron-sandbox/window.js').ILoadOptions; + type ILoadResult = import('../../../platform/window/electron-browser/window.js').ILoadResult; + type ILoadOptions = import('../../../platform/window/electron-browser/window.js').ILoadOptions; type INativeWindowConfiguration = import('../../../platform/window/common/window.ts').INativeWindowConfiguration; - type IMainWindowSandboxGlobals = import('../../../base/parts/sandbox/electron-sandbox/globals.js').IMainWindowSandboxGlobals; - type IDesktopMain = import('../../../workbench/electron-sandbox/desktop.main.js').IDesktopMain; + type IMainWindowSandboxGlobals = import('../../../base/parts/sandbox/electron-browser/globals.js').IMainWindowSandboxGlobals; + type IDesktopMain = import('../../../workbench/electron-browser/desktop.main.js').IDesktopMain; const preloadGlobals: IMainWindowSandboxGlobals = (window as any).vscode; // defined by preload.ts const safeProcess = preloadGlobals.process; diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index d5e39554d43..ce0cce592e4 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -58,7 +58,7 @@ import { ISignService } from '../../platform/sign/common/sign.js'; import { SignService } from '../../platform/sign/node/signService.js'; import { IStateReadService, IStateService } from '../../platform/state/node/state.js'; import { NullTelemetryService } from '../../platform/telemetry/common/telemetryUtils.js'; -import { IThemeMainService, ThemeMainService } from '../../platform/theme/electron-main/themeMainService.js'; +import { IThemeMainService } from '../../platform/theme/electron-main/themeMainService.js'; import { IUserDataProfilesMainService, UserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js'; import { IPolicyService, NullPolicyService } from '../../platform/policy/common/policy.js'; import { NativePolicyService } from '../../platform/policy/node/nativePolicyService.js'; @@ -72,6 +72,7 @@ import { massageMessageBoxOptions } from '../../platform/dialogs/common/dialogs. import { SaveStrategy, StateService } from '../../platform/state/node/stateService.js'; import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; import { addUNCHostToAllowlist, getUNCHost } from '../../base/node/unc.js'; +import { ThemeMainService } from '../../platform/theme/electron-main/themeMainServiceImpl.js'; /** * The main VS Code entry point. diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index a056495bfef..61c0c1aab35 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -196,6 +196,17 @@ registerEditorSettingMigration('experimental.stickyScroll.maxLineCount', (value, } }); +// Edit Context + +registerEditorSettingMigration('editor.experimentalEditContextEnabled', (value, read, write) => { + if (typeof value === 'boolean') { + write('editor.experimentalEditContextEnabled', undefined); + if (typeof read('editor.editContext') === 'undefined') { + write('editor.editContext', value); + } + } +}); + // Code Actions on Save registerEditorSettingMigration('codeActionsOnSave', (value, read, write) => { if (value && typeof value === 'object') { diff --git a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts index a4e5865c011..252a44596ef 100644 --- a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts +++ b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts @@ -12,6 +12,7 @@ import { NKeyMap } from '../../../../base/common/map.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { MetadataConsts } from '../../../common/encodedTokenAttributes.js'; +import type { DecorationStyleCache } from '../css/decorationStyleCache.js'; import { GlyphRasterizer } from '../raster/glyphRasterizer.js'; import type { IGlyphRasterizer } from '../raster/raster.js'; import { IdleTaskQueue, type ITaskQueue } from '../taskQueue.js'; @@ -59,6 +60,7 @@ export class TextureAtlas extends Disposable { /** The maximum texture size supported by the GPU. */ private readonly _maxTextureSize: number, options: ITextureAtlasOptions | undefined, + private readonly _decorationStyleCache: DecorationStyleCache, @IThemeService private readonly _themeService: IThemeService, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { @@ -88,7 +90,7 @@ export class TextureAtlas extends Disposable { // IMPORTANT: The first glyph on the first page must be an empty glyph such that zeroed out // cells end up rendering nothing // TODO: This currently means the first slab is for 0x0 glyphs and is wasted - const nullRasterizer = new GlyphRasterizer(1, '', 1); + const nullRasterizer = new GlyphRasterizer(1, '', 1, this._decorationStyleCache); firstPage.getGlyph(nullRasterizer, '', 0, 0); nullRasterizer.dispose(); } diff --git a/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts b/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts index d06871cc24d..cc41c8dcaa4 100644 --- a/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts +++ b/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts @@ -8,8 +8,8 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { StringBuilder } from '../../../common/core/stringBuilder.js'; import { FontStyle, TokenMetadata } from '../../../common/encodedTokenAttributes.js'; +import type { DecorationStyleCache } from '../css/decorationStyleCache.js'; import { ensureNonNullable } from '../gpuUtils.js'; -import { ViewGpuContext } from '../viewGpuContext.js'; import { type IBoundingBox, type IGlyphRasterizer, type IRasterizedGlyph } from './raster.js'; let nextId = 0; @@ -50,7 +50,8 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { constructor( readonly fontSize: number, readonly fontFamily: string, - readonly devicePixelRatio: number + readonly devicePixelRatio: number, + private readonly _decorationStyleCache: DecorationStyleCache, ) { super(); @@ -119,7 +120,7 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { const bgId = TokenMetadata.getBackground(tokenMetadata); const bg = colorMap[bgId]; - const decorationStyleSet = ViewGpuContext.decorationStyleCache.getStyleSet(decorationStyleSetId); + const decorationStyleSet = this._decorationStyleCache.getStyleSet(decorationStyleSetId); // When SPAA is used, the background color must be present to get the right glyph if (this._antiAliasing === 'subpixel') { diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 181c468f12c..953e7c23f7a 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -24,13 +24,12 @@ import { Event } from '../../../base/common/event.js'; import { EditorOption, type IEditorOptions } from '../../common/config/editorOptions.js'; import { InlineDecorationType } from '../../common/viewModel.js'; import { DecorationStyleCache } from './css/decorationStyleCache.js'; -import { ViewportRenderStrategy } from './renderStrategy/viewportRenderStrategy.js'; export class ViewGpuContext extends Disposable { /** * The hard cap for line columns rendered by the GPU renderer. */ - readonly maxGpuCols = ViewportRenderStrategy.maxSupportedColumns; + readonly maxGpuCols = 2000; readonly canvas: FastDomNode; readonly ctx: GPUCanvasContext; @@ -111,7 +110,7 @@ export class ViewGpuContext extends Disposable { }).then(ref => { ViewGpuContext.deviceSync = ref.object; if (!ViewGpuContext._atlas) { - ViewGpuContext._atlas = this._instantiationService.createInstance(TextureAtlas, ref.object.limits.maxTextureDimension2D, undefined); + ViewGpuContext._atlas = this._instantiationService.createInstance(TextureAtlas, ref.object.limits.maxTextureDimension2D, undefined, ViewGpuContext.decorationStyleCache); } return ref.object; }); diff --git a/src/vs/editor/browser/observableCodeEditor.ts b/src/vs/editor/browser/observableCodeEditor.ts index 42b6580e9bc..cf69c10810f 100644 --- a/src/vs/editor/browser/observableCodeEditor.ts +++ b/src/vs/editor/browser/observableCodeEditor.ts @@ -148,6 +148,7 @@ export class ObservableCodeEditor extends Disposable { this.layoutInfoVerticalScrollbarWidth = this.layoutInfo.map(l => l.verticalScrollbarWidth); this.contentWidth = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentWidth()); this.contentHeight = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentHeight()); + this._widgetCounter = 0; this.openedPeekWidgets = observableValue(this, 0); @@ -203,6 +204,11 @@ export class ObservableCodeEditor extends Disposable { this._endUpdate(); } })); + + this.domNode = derived(reader => { + this.model.read(reader); + return this.editor.getDomNode(); + }); } public forceUpdate(): void; @@ -272,6 +278,8 @@ export class ObservableCodeEditor extends Disposable { public readonly contentWidth; public readonly contentHeight; + public readonly domNode; + public getOption(id: T): IObservable> { return observableFromEvent(this, cb => this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(id)) { cb(undefined); } @@ -403,6 +411,51 @@ export class ObservableCodeEditor extends Disposable { })); return isHovered; } + + observeLineHeightForPosition(position: IObservable | Position): IObservable; + observeLineHeightForPosition(position: IObservable): IObservable; + observeLineHeightForPosition(position: IObservable | Position): IObservable { + return derived(reader => { + const pos = position instanceof Position ? position : position.read(reader); + if (pos === null) { + return null; + } + + this.getOption(EditorOption.lineHeight).read(reader); + + return this.editor.getLineHeightForPosition(pos); + }); + } + + observeLineHeightForLine(lineNumber: IObservable | number): IObservable; + observeLineHeightForLine(lineNumber: IObservable): IObservable; + observeLineHeightForLine(lineNumber: IObservable | number): IObservable { + if (typeof lineNumber === 'number') { + return this.observeLineHeightForPosition(new Position(lineNumber, 1)); + } + + return derived(reader => { + const line = lineNumber.read(reader); + if (line === null) { + return null; + } + + return this.observeLineHeightForPosition(new Position(line, 1)).read(reader); + }); + } + + observeLineHeightsForLineRange(lineNumber: IObservable | LineRange): IObservable { + return derived(reader => { + const range = lineNumber instanceof LineRange ? lineNumber : lineNumber.read(reader); + + const heights: number[] = []; + for (let i = range.startLineNumber; i < range.endLineNumberExclusive; i++) { + heights.push(this.observeLineHeightForLine(i).read(reader)); + } + return heights; + }); + } + } interface IObservableOverlayWidget { diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index 76ed0450a63..77cc168f558 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -82,7 +82,7 @@ export abstract class EditorWorkerService extends Disposable implements IEditorW return links && { links }; } })); - this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, this._languageConfigurationService))); + this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, this._languageConfigurationService, this._logService))); } public override dispose(): void { @@ -240,7 +240,8 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide workerManager: WorkerManager, configurationService: ITextResourceConfigurationService, modelService: IModelService, - private readonly languageConfigurationService: ILanguageConfigurationService + private readonly languageConfigurationService: ILanguageConfigurationService, + private readonly logService: ILogService ) { this._workerManager = workerManager; this._configurationService = configurationService; @@ -286,6 +287,9 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); const insert = replace.setEndPosition(position.lineNumber, position.column); + // Trace logging about the word and replace/insert ranges + this.logService.trace('[WordBasedCompletionItemProvider]', `word: "${word?.word || ''}", wordDef: "${wordDefRegExp}", replace: [${replace.toString()}], insert: [${insert.toString()}]`); + const client = await this._workerManager.withWorker(); const data = await client.textualSuggest(models, word?.word, wordDefRegExp); if (!data) { diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 53acaf14f7f..ef187061795 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -103,7 +103,7 @@ export class View extends ViewEventHandler { private readonly _viewParts: ViewPart[]; private readonly _viewController: ViewController; - private _experimentalEditContextEnabled: boolean; + private _editContextEnabled: boolean; private _accessibilitySupport: AccessibilitySupport; private _editContext: AbstractEditContext; private readonly _pointerHandler: PointerHandler; @@ -157,7 +157,7 @@ export class View extends ViewEventHandler { this._viewParts = []; // Keyboard handler - this._experimentalEditContextEnabled = this._context.configuration.options.get(EditorOption.effectiveExperimentalEditContextEnabled); + this._editContextEnabled = this._context.configuration.options.get(EditorOption.effectiveEditContext); this._accessibilitySupport = this._context.configuration.options.get(EditorOption.accessibilitySupport); this._editContext = this._instantiateEditContext(); @@ -289,7 +289,7 @@ export class View extends ViewEventHandler { } private _instantiateEditContext(): AbstractEditContext { - const usingExperimentalEditContext = this._context.configuration.options.get(EditorOption.effectiveExperimentalEditContextEnabled); + const usingExperimentalEditContext = this._context.configuration.options.get(EditorOption.effectiveEditContext); if (usingExperimentalEditContext) { return this._instantiationService.createInstance(NativeEditContext, this._ownerID, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper()); } else { @@ -298,12 +298,12 @@ export class View extends ViewEventHandler { } private _updateEditContext(): void { - const experimentalEditContextEnabled = this._context.configuration.options.get(EditorOption.effectiveExperimentalEditContextEnabled); + const editContextEnabled = this._context.configuration.options.get(EditorOption.effectiveEditContext); const accessibilitySupport = this._context.configuration.options.get(EditorOption.accessibilitySupport); - if (this._experimentalEditContextEnabled === experimentalEditContextEnabled && this._accessibilitySupport === accessibilitySupport) { + if (this._editContextEnabled === editContextEnabled && this._accessibilitySupport === accessibilitySupport) { return; } - this._experimentalEditContextEnabled = experimentalEditContextEnabled; + this._editContextEnabled = editContextEnabled; this._accessibilitySupport = accessibilitySupport; const isEditContextFocused = this._editContext.isFocused(); const indexOfEditContext = this._viewParts.indexOf(this._editContext); diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 583d7f9cfa0..b2efc74cf9f 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -6,7 +6,7 @@ import { createTrustedTypesPolicy } from '../../../base/browser/trustedTypes.js'; import { CharCode } from '../../../base/common/charCode.js'; import * as strings from '../../../base/common/strings.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { applyFontInfo } from '../config/domFontInfo.js'; import { WrappingIndent } from '../../common/config/editorOptions.js'; import { FontInfo } from '../../common/config/fontInfo.js'; @@ -35,7 +35,7 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory injectedTexts.push(injectedText); }, finalize: () => { - return createLineBreaks(assertIsDefined(this.targetWindow.deref()), requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts); + return createLineBreaks(assertReturnsDefined(this.targetWindow.deref()), requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts); } }; } diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 5e53d12d2e4..18eb3d7df39 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -133,7 +133,10 @@ export class ViewController { const options = this.configuration.options; const selectionClipboardIsOn = (platform.isLinux && options.get(EditorOption.selectionClipboard)); const columnSelection = options.get(EditorOption.columnSelection); - if (data.middleButton && !selectionClipboardIsOn) { + const scrollOnMiddleClick = options.get(EditorOption.scrollOnMiddleClick); + if (data.middleButton && !selectionClipboardIsOn && scrollOnMiddleClick) { + // nothing to do here, handled in the contribution + } else if (data.middleButton && !selectionClipboardIsOn && columnSelection) { this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode); } else if (data.startedOnLineNumbers) { // If the dragging started on the gutter, then have operations work on the entire line diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 82c46115a1b..d3fd3861bb2 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -35,7 +35,7 @@ export class ViewCursors extends ViewPart { private _cursorBlinking: TextEditorCursorBlinkingStyle; private _cursorStyle: TextEditorCursorStyle; private _cursorSmoothCaretAnimation: 'off' | 'explicit' | 'on'; - private _experimentalEditContextEnabled: boolean; + private _editContextEnabled: boolean; private _selectionIsEmpty: boolean; private _isComposingInput: boolean; @@ -61,7 +61,7 @@ export class ViewCursors extends ViewPart { this._cursorBlinking = options.get(EditorOption.cursorBlinking); this._cursorStyle = options.get(EditorOption.effectiveCursorStyle); this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation); - this._experimentalEditContextEnabled = options.get(EditorOption.effectiveExperimentalEditContextEnabled); + this._editContextEnabled = options.get(EditorOption.effectiveEditContext); this._selectionIsEmpty = true; this._isComposingInput = false; @@ -116,7 +116,7 @@ export class ViewCursors extends ViewPart { this._cursorBlinking = options.get(EditorOption.cursorBlinking); this._cursorStyle = options.get(EditorOption.effectiveCursorStyle); this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation); - this._experimentalEditContextEnabled = options.get(EditorOption.effectiveExperimentalEditContextEnabled); + this._editContextEnabled = options.get(EditorOption.effectiveEditContext); this._updateBlinking(); this._updateDomClassName(); @@ -226,7 +226,7 @@ export class ViewCursors extends ViewPart { private _getCursorBlinking(): TextEditorCursorBlinkingStyle { // TODO: Remove the following if statement when experimental edit context is made default sole implementation - if (this._isComposingInput && !this._experimentalEditContextEnabled) { + if (this._isComposingInput && !this._editContextEnabled) { // avoid double cursors return TextEditorCursorBlinkingStyle.Hidden; } diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index dd0666b3e3b..dca6206ec1f 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -196,7 +196,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily); const fontSize = this._context.configuration.options.get(EditorOption.fontSize); - this._glyphRasterizer.value = this._register(new GlyphRasterizer(fontSize, fontFamily, this._viewGpuContext.devicePixelRatio.get())); + this._glyphRasterizer.value = this._register(new GlyphRasterizer(fontSize, fontFamily, this._viewGpuContext.devicePixelRatio.get(), ViewGpuContext.decorationStyleCache)); this._register(runOnChange(this._viewGpuContext.devicePixelRatio, () => { this._refreshGlyphRasterizer(); })); @@ -454,7 +454,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { glyphRasterizer.fontSize !== fontSize || glyphRasterizer.devicePixelRatio !== devicePixelRatio ) { - this._glyphRasterizer.value = new GlyphRasterizer(fontSize, fontFamily, devicePixelRatio); + this._glyphRasterizer.value = new GlyphRasterizer(fontSize, fontFamily, devicePixelRatio, ViewGpuContext.decorationStyleCache); } } diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css index 640909467f4..9e89061045d 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css @@ -3,70 +3,75 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-component.diff-review { - user-select: none; - -webkit-user-select: none; - z-index: 99; -} - .monaco-diff-editor .diff-review { position: absolute; } -.monaco-component.diff-review .diff-review-line-number { - text-align: right; - display: inline-block; - color: var(--vscode-editorLineNumber-foreground); -} +.monaco-component.diff-review { + user-select: none; + -webkit-user-select: none; + z-index: 99; -.monaco-component.diff-review .diff-review-summary { - padding-left: 10px; -} -.monaco-component.diff-review .diff-review-shadow { - position: absolute; - box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset; -} + .diff-review-line-number { + text-align: right; + display: inline-block; + color: var(--vscode-editorLineNumber-foreground); + } -.monaco-component.diff-review .diff-review-row { - white-space: pre; -} + .diff-review-summary { + padding-left: 10px; + } -.monaco-component.diff-review .diff-review-table { - display: table; - min-width: 100%; -} + .diff-review-shadow { + position: absolute; + box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset; + } -.monaco-component.diff-review .diff-review-row { - display: table-row; - width: 100%; -} + .diff-review-row { + white-space: pre; + } -.monaco-component.diff-review .diff-review-spacer { - display: inline-block; - width: 10px; - vertical-align: middle; -} + .diff-review-table { + display: table; + min-width: 100%; + } -.monaco-component.diff-review .diff-review-spacer > .codicon { - font-size: 9px !important; -} + .diff-review-row { + display: table-row; + width: 100%; + } -.monaco-component.diff-review .diff-review-actions { - display: inline-block; - position: absolute; - right: 10px; - top: 2px; - z-index: 100; -} + .diff-review-spacer { + display: inline-block; + width: 10px; + vertical-align: middle; + } -.monaco-component.diff-review .diff-review-actions .action-label { - width: 16px; - height: 16px; - margin: 2px 0; -} + .diff-review-spacer > .codicon { + font-size: 9px !important; + } -.monaco-component.diff-review .revertButton { - cursor: pointer; + .diff-review-actions { + display: inline-block; + position: absolute; + right: 10px; + top: 2px; + z-index: 100; + } + + .diff-review-actions .action-label { + width: 16px; + height: 16px; + margin: 2px 0; + } + + .revertButton { + cursor: pointer; + } + + .action-label { + background: var(--vscode-editorActionList-background); + } } diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index ce4565f526f..0a2fd9eda46 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -10,7 +10,7 @@ import { Codicon } from '../../../../../../base/common/codicons.js'; import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { IObservable, autorun, derived, observableFromEvent, observableValue } from '../../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import { assertIsDefined } from '../../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../../base/common/types.js'; import { applyFontInfo } from '../../../../config/domFontInfo.js'; import { CodeEditorWidget } from '../../../codeEditor/codeEditorWidget.js'; import { diffDeleteDecoration, diffRemoveIcon } from '../../registrations.contribution.js'; @@ -239,7 +239,7 @@ export class DiffEditorViewZones extends Disposable { let zoneId: string | undefined = undefined; alignmentViewZonesDisposables.add( new InlineDiffDeletedCodeMargin( - () => assertIsDefined(zoneId), + () => assertReturnsDefined(zoneId), marginDomNode, this._editors.modified, a.diff, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index bc577bd8807..db1ac7e59b1 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -294,6 +294,10 @@ export interface IEditorOptions { * Defaults to true. */ scrollBeyondLastLine?: boolean; + /** + * Scroll editor on middle click + */ + scrollOnMiddleClick?: boolean; /** * Enable that scrolling can go beyond the last column by a number of columns. * Defaults to 5. @@ -507,6 +511,14 @@ export interface IEditorOptions { * Defaults to advanced. */ autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; + /** + * Boolean which controls whether to autoindent on paste + */ + autoIndentOnPaste?: boolean; + /** + * Boolean which controls whether to autoindent on paste within a string when autoIndentOnPaste is enabled. + */ + autoIndentOnPasteWithinString?: boolean; /** * Emulate selection behaviour of tab characters when using spaces for indentation. * This means selection will stick to tab stops. @@ -770,7 +782,7 @@ export interface IEditorOptions { /** * Sets whether the new experimental edit context should be used instead of the text area. */ - experimentalEditContextEnabled?: boolean; + editContext?: boolean; /** * Controls support for changing how content is pasted into the editor. @@ -1958,14 +1970,14 @@ class EffectiveCursorStyle extends ComputedEditorOption { +class EffectiveEditContextEnabled extends ComputedEditorOption { constructor() { - super(EditorOption.effectiveExperimentalEditContextEnabled); + super(EditorOption.effectiveEditContext); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions): boolean { - return env.editContextSupported && options.get(EditorOption.experimentalEditContextEnabled); + return env.editContextSupported && options.get(EditorOption.editContext); } } @@ -5503,6 +5515,8 @@ export const enum EditorOption { autoClosingOvertype, autoClosingQuotes, autoIndent, + autoIndentOnPaste, + autoIndentOnPasteWithinString, automaticLayout, autoSurround, bracketPairColorization, @@ -5527,7 +5541,7 @@ export const enum EditorOption { domReadOnly, dragAndDrop, dropIntoEditor, - experimentalEditContextEnabled, + editContext, emptySelectionClipboard, experimentalGpuAcceleration, experimentalWhitespaceRendering, @@ -5647,7 +5661,8 @@ export const enum EditorOption { defaultColorDecorators, colorDecoratorsActivatedOn, inlineCompletionsAccessibilityVerbose, - effectiveExperimentalEditContextEnabled + effectiveEditContext, + scrollOnMiddleClick, } export const EditorOptions = { @@ -5774,6 +5789,14 @@ export const EditorOptions = { description: nls.localize('autoIndent', "Controls whether the editor should automatically adjust the indentation when users type, paste, move or indent lines.") } )), + autoIndentOnPaste: register(new EditorBooleanOption( + EditorOption.autoIndentOnPaste, 'autoIndentOnPaste', false, + { description: nls.localize('autoIndentOnPaste', "Controls whether the editor should automatically auto-indent the pasted content.") } + )), + autoIndentOnPasteWithinString: register(new EditorBooleanOption( + EditorOption.autoIndentOnPasteWithinString, 'autoIndentOnPasteWithinString', true, + { description: nls.localize('autoIndentOnPasteWithinString', "Controls whether the editor should automatically auto-indent the pasted content when pasted within a string. This takes effect when autoIndentOnPaste is true.") } + )), automaticLayout: register(new EditorBooleanOption( EditorOption.automaticLayout, 'automaticLayout', false, )), @@ -5913,10 +5936,10 @@ export const EditorOptions = { )), emptySelectionClipboard: register(new EditorEmptySelectionClipboard()), dropIntoEditor: register(new EditorDropIntoEditor()), - experimentalEditContextEnabled: register(new EditorBooleanOption( - EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', true, + editContext: register(new EditorBooleanOption( + EditorOption.editContext, 'editContext', true, { - description: nls.localize('experimentalEditContextEnabled', "Sets whether the new experimental edit context should be used instead of the text area."), + description: nls.localize('editContext', "Sets whether the EditContext API should be used instead of the text area to power input in the editor."), included: platform.isChrome || platform.isEdge || platform.isNative } )), @@ -6243,6 +6266,10 @@ export const EditorOptions = { EditorOption.scrollBeyondLastLine, 'scrollBeyondLastLine', true, { description: nls.localize('scrollBeyondLastLine', "Controls whether the editor will scroll beyond the last line.") } )), + scrollOnMiddleClick: register(new EditorBooleanOption( + EditorOption.scrollOnMiddleClick, 'scrollOnMiddleClick', false, + { description: nls.localize('scrollOnMiddleClick', "Controls whether the editor will scroll when the middle button is pressed.") } + )), scrollPredominantAxis: register(new EditorBooleanOption( EditorOption.scrollPredominantAxis, 'scrollPredominantAxis', true, { description: nls.localize('scrollPredominantAxis', "Scroll only along the predominant axis when scrolling both vertically and horizontally at the same time. Prevents horizontal drift when scrolling vertically on a trackpad.") } @@ -6480,7 +6507,7 @@ export const EditorOptions = { wrappingInfo: register(new EditorWrappingInfoComputer()), wrappingIndent: register(new WrappingIndentOption()), wrappingStrategy: register(new WrappingStrategy()), - effectiveExperimentalEditContextEnabled: register(new EffectiveExperimentalEditContextEnabled()) + effectiveEditContextEnabled: register(new EffectiveEditContextEnabled()) }; type EditorOptionsType = typeof EditorOptions; diff --git a/src/vs/editor/common/core/2d/point.ts b/src/vs/editor/common/core/2d/point.ts index 586367df62c..8d7c92696a1 100644 --- a/src/vs/editor/common/core/2d/point.ts +++ b/src/vs/editor/common/core/2d/point.ts @@ -28,4 +28,31 @@ export class Point { public toString() { return `(${this.x},${this.y})`; } + + public subtract(other: Point): Point { + return new Point(this.x - other.x, this.y - other.y); + } + + public scale(factor: number): Point { + return new Point(this.x * factor, this.y * factor); + } + + public mapComponents(map: (value: number) => number): Point { + return new Point(map(this.x), map(this.y)); + } + + public isZero(): boolean { + return this.x === 0 && this.y === 0; + } + + public withThreshold(threshold: number): Point { + return this.mapComponents(axisVal => { + if (axisVal > threshold) { + return axisVal - threshold; + } else if (axisVal < -threshold) { + return axisVal + threshold; + } + return 0; + }); + } } diff --git a/src/vs/editor/common/core/edits/edit.ts b/src/vs/editor/common/core/edits/edit.ts index c364e96bec9..44969660791 100644 --- a/src/vs/editor/common/core/edits/edit.ts +++ b/src/vs/editor/common/core/edits/edit.ts @@ -254,6 +254,40 @@ export abstract class BaseEdit, TEdit extends BaseE } return postEditsOffset - accumulatedDelta; } + + /** + * Return undefined if the originalOffset is within an edit + */ + public applyToOffsetOrUndefined(originalOffset: number): number | undefined { + let accumulatedDelta = 0; + for (const edit of this.replacements) { + if (edit.replaceRange.start <= originalOffset) { + if (originalOffset < edit.replaceRange.endExclusive) { + // the offset is in the replaced range + return undefined; + } + accumulatedDelta += edit.getNewLength() - edit.replaceRange.length; + } else { + break; + } + } + return originalOffset + accumulatedDelta; + } + + /** + * Return undefined if the originalRange is within an edit + */ + public applyToOffsetRangeOrUndefined(originalRange: OffsetRange): OffsetRange | undefined { + const start = this.applyToOffsetOrUndefined(originalRange.start); + if (start === undefined) { + return undefined; + } + const end = this.applyToOffsetOrUndefined(originalRange.endExclusive); + if (end === undefined) { + return undefined; + } + return new OffsetRange(start, end); + } } export abstract class BaseReplacement> { diff --git a/src/vs/editor/common/core/edits/lineEdit.ts b/src/vs/editor/common/core/edits/lineEdit.ts index c7dd4b6dd55..6086f923b3f 100644 --- a/src/vs/editor/common/core/edits/lineEdit.ts +++ b/src/vs/editor/common/core/edits/lineEdit.ts @@ -17,7 +17,7 @@ export class LineEdit { public static readonly empty = new LineEdit([]); public static deserialize(data: SerializedLineEdit): LineEdit { - return new LineEdit(data.map(e => SingleLineEdit.deserialize(e))); + return new LineEdit(data.map(e => LineReplacement.deserialize(e))); } public static fromEdit(edit: StringEdit, initialValue: AbstractText): LineEdit { @@ -28,7 +28,7 @@ export class LineEdit { public static fromTextEdit(edit: TextEdit, initialValue: AbstractText): LineEdit { const edits = edit.replacements; - const result: SingleLineEdit[] = []; + const result: LineReplacement[] = []; const currentEdits: TextReplacement[] = []; for (let i = 0; i < edits.length; i++) { @@ -42,14 +42,14 @@ export class LineEdit { const singleEdit = TextReplacement.joinReplacements(currentEdits, initialValue); currentEdits.length = 0; - const singleLineEdit = SingleLineEdit.fromSingleTextEdit(singleEdit, initialValue); + const singleLineEdit = LineReplacement.fromSingleTextEdit(singleEdit, initialValue); result.push(singleLineEdit); } return new LineEdit(result); } - public static createFromUnsorted(edits: readonly SingleLineEdit[]): LineEdit { + public static createFromUnsorted(edits: readonly LineReplacement[]): LineEdit { const result = edits.slice(); result.sort(compareBy(i => i.lineRange.startLineNumber, numberComparator)); return new LineEdit(result); @@ -59,11 +59,15 @@ export class LineEdit { /** * Have to be sorted by start line number and non-intersecting. */ - public readonly edits: readonly SingleLineEdit[] + public readonly edits: readonly LineReplacement[] ) { assert(checkAdjacentItems(edits, (i1, i2) => i1.lineRange.endLineNumberExclusive <= i2.lineRange.startLineNumber)); } + public isEmpty(): boolean { + return this.edits.length === 0; + } + public toEdit(initialValue: AbstractText): StringEdit { const edits: StringReplacement[] = []; for (const edit of this.edits) { @@ -110,9 +114,20 @@ export class LineEdit { ); } + + /** TODO improve, dont require originalLines */ + public mapBackLineRange(lineRange: LineRange, originalLines: string[]): LineRange { + const i = this.inverse(originalLines); + return i.mapLineRange(lineRange); + } + + public touches(other: LineEdit): boolean { + return this.edits.some(e1 => other.edits.some(e2 => e1.lineRange.intersect(e2.lineRange))); + } + public rebase(base: LineEdit): LineEdit { return new LineEdit( - this.edits.map(e => new SingleLineEdit(base.mapLineRange(e.lineRange), e.newLines)), + this.edits.map(e => new LineReplacement(base.mapLineRange(e.lineRange), e.newLines)), ); } @@ -203,20 +218,24 @@ export class LineEdit { return result; } - public toSingleEdit() { - + public inverse(originalLines: string[]): LineEdit { + const newRanges = this.getNewLineRanges(); + return new LineEdit(this.edits.map((e, idx) => new LineReplacement( + newRanges[idx], + originalLines.slice(e.lineRange.startLineNumber - 1, e.lineRange.endLineNumberExclusive - 1), + ))); } } -export class SingleLineEdit { - public static deserialize(e: SerializedSingleLineEdit): SingleLineEdit { - return new SingleLineEdit( +export class LineReplacement { + public static deserialize(e: SerializedLineReplacement): LineReplacement { + return new LineReplacement( LineRange.ofLength(e[0], e[1] - e[0]), e[2], ); } - public static fromSingleTextEdit(edit: TextReplacement, initialValue: AbstractText): SingleLineEdit { + public static fromSingleTextEdit(edit: TextReplacement, initialValue: AbstractText): LineReplacement { // 1: ab[cde // 2: fghijk // 3: lmn]opq @@ -262,7 +281,7 @@ export class SingleLineEdit { newLines.pop(); } - return new SingleLineEdit(new LineRange(startLineNumber, endLineNumberEx), newLines); + return new LineReplacement(new LineRange(startLineNumber, endLineNumberEx), newLines); } constructor( @@ -335,7 +354,7 @@ export class SingleLineEdit { return `${this.lineRange}->${JSON.stringify(this.newLines)}`; } - public serialize(): SerializedSingleLineEdit { + public serialize(): SerializedLineReplacement { return [ this.lineRange.startLineNumber, this.lineRange.endLineNumberExclusive, @@ -343,7 +362,7 @@ export class SingleLineEdit { ]; } - public removeCommonSuffixPrefixLines(initialValue: AbstractText): SingleLineEdit { + public removeCommonSuffixPrefixLines(initialValue: AbstractText): LineReplacement { let startLineNumber = this.lineRange.startLineNumber; let endLineNumberEx = this.lineRange.endLineNumberExclusive; @@ -368,7 +387,7 @@ export class SingleLineEdit { if (trimStartCount === 0 && trimEndCount === 0) { return this; } - return new SingleLineEdit(new LineRange(startLineNumber, endLineNumberEx), this.newLines.slice(trimStartCount, this.newLines.length - trimEndCount)); + return new LineReplacement(new LineRange(startLineNumber, endLineNumberEx), this.newLines.slice(trimStartCount, this.newLines.length - trimEndCount)); } public toLineEdit(): LineEdit { @@ -376,5 +395,18 @@ export class SingleLineEdit { } } -export type SerializedLineEdit = SerializedSingleLineEdit[]; -export type SerializedSingleLineEdit = [startLineNumber: number, endLineNumber: number, newLines: readonly string[]]; +export type SerializedLineEdit = SerializedLineReplacement[]; +export type SerializedLineReplacement = [startLineNumber: number, endLineNumber: number, newLines: readonly string[]]; + +export namespace SerializedLineReplacement { + export function is(thing: unknown): thing is SerializedLineReplacement { + return ( + Array.isArray(thing) + && thing.length === 3 + && typeof thing[0] === 'number' + && typeof thing[1] === 'number' + && Array.isArray(thing[2]) + && thing[2].every((e: any) => typeof e === 'string') + ); + } +} diff --git a/src/vs/editor/common/core/edits/stringEdit.ts b/src/vs/editor/common/core/edits/stringEdit.ts index 2c998e7e37e..f7763163166 100644 --- a/src/vs/editor/common/core/edits/stringEdit.ts +++ b/src/vs/editor/common/core/edits/stringEdit.ts @@ -158,6 +158,10 @@ export class StringEdit extends BaseEdit { } return new StringEdit(edits); } + + public normalizeEOL(eol: '\r\n' | '\n'): StringEdit { + return new StringEdit(this.replacements.map(edit => edit.normalizeEOL(eol))); + } } /** @@ -183,6 +187,10 @@ export class StringReplacement extends BaseReplacement { return new StringReplacement(range, text); } + public static delete(range: OffsetRange): StringReplacement { + return new StringReplacement(range, ''); + } + public static fromJson(data: ISerializedStringReplacement): StringReplacement { return new StringReplacement(OffsetRange.ofStartAndLength(data.pos, data.len), data.txt); } @@ -241,6 +249,11 @@ export class StringReplacement extends BaseReplacement { return new StringReplacement(replaceRange, newText); } + + normalizeEOL(eol: '\r\n' | '\n'): StringReplacement { + const newText = this.newText.replace(/\r\n|\n/g, eol); + return new StringReplacement(this.replaceRange, newText); + } } export function applyEditsToRanges(sortedRanges: OffsetRange[], edit: StringEdit): OffsetRange[] { diff --git a/src/vs/editor/common/core/text/abstractText.ts b/src/vs/editor/common/core/text/abstractText.ts index 274a05f2e0e..fe99d9f818d 100644 --- a/src/vs/editor/common/core/text/abstractText.ts +++ b/src/vs/editor/common/core/text/abstractText.ts @@ -6,7 +6,7 @@ import { assert } from '../../../../base/common/assert.js'; import { splitLines } from '../../../../base/common/strings.js'; import { Position } from '../position.js'; -import { PositionOffsetTransformer } from './positionToOffset.js'; +import { PositionOffsetTransformer } from './positionToOffsetImpl.js'; import { Range } from '../range.js'; import { LineRange } from '../ranges/lineRange.js'; import { TextLength } from '../text/textLength.js'; @@ -48,6 +48,17 @@ export abstract class AbstractText { const value = this.getValue(); return splitLines(value); } + + getLinesOfRange(range: LineRange): string[] { + return range.mapToLineArray(lineNumber => this.getLineAt(lineNumber)); + } + + equals(other: AbstractText): boolean { + if (this === other) { + return true; + } + return this.getValue() === other.getValue(); + } } export class LineBasedText extends AbstractText { diff --git a/src/vs/editor/common/core/text/positionToOffset.ts b/src/vs/editor/common/core/text/positionToOffset.ts index ed0e5f879ba..97d6ee3bf7c 100644 --- a/src/vs/editor/common/core/text/positionToOffset.ts +++ b/src/vs/editor/common/core/text/positionToOffset.ts @@ -3,117 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { findLastIdxMonotonous } from '../../../../base/common/arraysFind.js'; import { StringEdit, StringReplacement } from '../edits/stringEdit.js'; -import { OffsetRange } from '../ranges/offsetRange.js'; -import { Position } from '../position.js'; -import { Range } from '../range.js'; -import { TextReplacement, TextEdit } from '../edits/textEdit.js'; -import { TextLength } from '../text/textLength.js'; +import { TextEdit, TextReplacement } from '../edits/textEdit.js'; +import { _setPositionOffsetTransformerDependencies } from './positionToOffsetImpl.js'; +import { TextLength } from './textLength.js'; -export abstract class PositionOffsetTransformerBase { - abstract getOffset(position: Position): number; +export { PositionOffsetTransformerBase, PositionOffsetTransformer } from './positionToOffsetImpl.js'; - getOffsetRange(range: Range): OffsetRange { - return new OffsetRange( - this.getOffset(range.getStartPosition()), - this.getOffset(range.getEndPosition()) - ); - } - - abstract getPosition(offset: number): Position; - - getRange(offsetRange: OffsetRange): Range { - return Range.fromPositions( - this.getPosition(offsetRange.start), - this.getPosition(offsetRange.endExclusive) - ); - } - - getStringEdit(edit: TextEdit): StringEdit { - const edits = edit.replacements.map(e => this.getStringReplacement(e)); - return new StringEdit(edits); - } - - getStringReplacement(edit: TextReplacement): StringReplacement { - return new StringReplacement(this.getOffsetRange(edit.range), edit.text); - } - - getSingleTextEdit(edit: StringReplacement): TextReplacement { - return new TextReplacement(this.getRange(edit.replaceRange), edit.newText); - } - - getTextEdit(edit: StringEdit): TextEdit { - const edits = edit.replacements.map(e => this.getSingleTextEdit(e)); - return new TextEdit(edits); - } -} - -export class PositionOffsetTransformer extends PositionOffsetTransformerBase { - private readonly lineStartOffsetByLineIdx: number[]; - private readonly lineEndOffsetByLineIdx: number[]; - - constructor(public readonly text: string) { - super(); - - this.lineStartOffsetByLineIdx = []; - this.lineEndOffsetByLineIdx = []; - - this.lineStartOffsetByLineIdx.push(0); - for (let i = 0; i < text.length; i++) { - if (text.charAt(i) === '\n') { - this.lineStartOffsetByLineIdx.push(i + 1); - if (i > 0 && text.charAt(i - 1) === '\r') { - this.lineEndOffsetByLineIdx.push(i - 1); - } else { - this.lineEndOffsetByLineIdx.push(i); - } - } - } - this.lineEndOffsetByLineIdx.push(text.length); - } - - override getOffset(position: Position): number { - const valPos = this._validatePosition(position); - return this.lineStartOffsetByLineIdx[valPos.lineNumber - 1] + valPos.column - 1; - } - - private _validatePosition(position: Position): Position { - if (position.lineNumber < 1) { - return new Position(1, 1); - } - const lineCount = this.textLength.lineCount + 1; - if (position.lineNumber > lineCount) { - const lineLength = this.getLineLength(lineCount); - return new Position(lineCount, lineLength + 1); - } - if (position.column < 1) { - return new Position(position.lineNumber, 1); - } - const lineLength = this.getLineLength(position.lineNumber); - if (position.column - 1 > lineLength) { - return new Position(position.lineNumber, lineLength + 1); - } - return position; - } - - override getPosition(offset: number): Position { - const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); - const lineNumber = idx + 1; - const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; - return new Position(lineNumber, column); - } - - getTextLength(offsetRange: OffsetRange): TextLength { - return TextLength.ofRange(this.getRange(offsetRange)); - } - - get textLength(): TextLength { - const lineIdx = this.lineStartOffsetByLineIdx.length - 1; - return new TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); - } - - getLineLength(lineNumber: number): number { - return this.lineEndOffsetByLineIdx[lineNumber - 1] - this.lineStartOffsetByLineIdx[lineNumber - 1]; - } -} +_setPositionOffsetTransformerDependencies({ + StringEdit: StringEdit, + StringReplacement: StringReplacement, + TextReplacement: TextReplacement, + TextEdit: TextEdit, + TextLength: TextLength, +}); diff --git a/src/vs/editor/common/core/text/positionToOffsetImpl.ts b/src/vs/editor/common/core/text/positionToOffsetImpl.ts new file mode 100644 index 00000000000..deb847b1322 --- /dev/null +++ b/src/vs/editor/common/core/text/positionToOffsetImpl.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { findLastIdxMonotonous } from '../../../../base/common/arraysFind.js'; +import { StringEdit, StringReplacement } from '../edits/stringEdit.js'; +import { OffsetRange } from '../ranges/offsetRange.js'; +import { Position } from '../position.js'; +import { Range } from '../range.js'; +import type { TextReplacement, TextEdit } from '../edits/textEdit.js'; +import type { TextLength } from '../text/textLength.js'; + +export abstract class PositionOffsetTransformerBase { + abstract getOffset(position: Position): number; + + getOffsetRange(range: Range): OffsetRange { + return new OffsetRange( + this.getOffset(range.getStartPosition()), + this.getOffset(range.getEndPosition()) + ); + } + + abstract getPosition(offset: number): Position; + + getRange(offsetRange: OffsetRange): Range { + return Range.fromPositions( + this.getPosition(offsetRange.start), + this.getPosition(offsetRange.endExclusive) + ); + } + + getStringEdit(edit: TextEdit): StringEdit { + const edits = edit.replacements.map(e => this.getStringReplacement(e)); + return new Deps.deps.StringEdit(edits); + } + + getStringReplacement(edit: TextReplacement): StringReplacement { + return new Deps.deps.StringReplacement(this.getOffsetRange(edit.range), edit.text); + } + + getSingleTextEdit(edit: StringReplacement): TextReplacement { + return new Deps.deps.TextReplacement(this.getRange(edit.replaceRange), edit.newText); + } + + getTextEdit(edit: StringEdit): TextEdit { + const edits = edit.replacements.map(e => this.getSingleTextEdit(e)); + return new Deps.deps.TextEdit(edits); + } +} + +interface IDeps { + StringEdit: typeof StringEdit; + StringReplacement: typeof StringReplacement; + TextReplacement: typeof TextReplacement; + TextEdit: typeof TextEdit; + TextLength: typeof TextLength; +} + +class Deps { + static _deps: IDeps | undefined = undefined; + static get deps(): IDeps { + if (!this._deps) { + throw new Error('Dependencies not set. Call _setDependencies first.'); + } + return this._deps; + } +} + +/** This is to break circular module dependencies. */ +export function _setPositionOffsetTransformerDependencies(deps: IDeps): void { + Deps._deps = deps; +} + +export class PositionOffsetTransformer extends PositionOffsetTransformerBase { + private readonly lineStartOffsetByLineIdx: number[]; + private readonly lineEndOffsetByLineIdx: number[]; + + constructor(public readonly text: string) { + super(); + + this.lineStartOffsetByLineIdx = []; + this.lineEndOffsetByLineIdx = []; + + this.lineStartOffsetByLineIdx.push(0); + for (let i = 0; i < text.length; i++) { + if (text.charAt(i) === '\n') { + this.lineStartOffsetByLineIdx.push(i + 1); + if (i > 0 && text.charAt(i - 1) === '\r') { + this.lineEndOffsetByLineIdx.push(i - 1); + } else { + this.lineEndOffsetByLineIdx.push(i); + } + } + } + this.lineEndOffsetByLineIdx.push(text.length); + } + + override getOffset(position: Position): number { + const valPos = this._validatePosition(position); + return this.lineStartOffsetByLineIdx[valPos.lineNumber - 1] + valPos.column - 1; + } + + private _validatePosition(position: Position): Position { + if (position.lineNumber < 1) { + return new Position(1, 1); + } + const lineCount = this.textLength.lineCount + 1; + if (position.lineNumber > lineCount) { + const lineLength = this.getLineLength(lineCount); + return new Position(lineCount, lineLength + 1); + } + if (position.column < 1) { + return new Position(position.lineNumber, 1); + } + const lineLength = this.getLineLength(position.lineNumber); + if (position.column - 1 > lineLength) { + return new Position(position.lineNumber, lineLength + 1); + } + return position; + } + + override getPosition(offset: number): Position { + const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); + const lineNumber = idx + 1; + const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; + return new Position(lineNumber, column); + } + + getTextLength(offsetRange: OffsetRange): TextLength { + return Deps.deps.TextLength.ofRange(this.getRange(offsetRange)); + } + + get textLength(): TextLength { + const lineIdx = this.lineStartOffsetByLineIdx.length - 1; + return new Deps.deps.TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); + } + + getLineLength(lineNumber: number): number { + return this.lineEndOffsetByLineIdx[lineNumber - 1] - this.lineStartOffsetByLineIdx[lineNumber - 1]; + } +} diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts index cd07eae5b0a..0da7983fc6c 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts @@ -141,7 +141,10 @@ export class DefaultLinesDiffComputer implements ILinesDiffComputer { scanForWhitespaceChanges(originalLines.length - seq1LastStart); - const changes = lineRangeMappingFromRangeMappings(alignments, new ArrayText(originalLines), new ArrayText(modifiedLines)); + const original = new ArrayText(originalLines); + const modified = new ArrayText(modifiedLines); + + const changes = lineRangeMappingFromRangeMappings(alignments, original, modified); let moves: MovedText[] = []; if (options.computeMoves) { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 3d0210dc4c4..ba900f3a584 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -153,7 +153,7 @@ export interface IContentSizeChangedEvent { export interface ITriggerEditorOperationEvent { source: string | null | undefined; handlerId: string; - payload: any; + payload: unknown; } export interface INewScrollPosition { diff --git a/src/vs/editor/common/languageFeatureRegistry.ts b/src/vs/editor/common/languageFeatureRegistry.ts index 6bf6f851720..3728cad3c41 100644 --- a/src/vs/editor/common/languageFeatureRegistry.ts +++ b/src/vs/editor/common/languageFeatureRegistry.ts @@ -139,7 +139,7 @@ export class LanguageFeatureRegistry { return result; } - private _orderedForEach(model: ITextModel, recursive: boolean, callback: (provider: Entry) => any): void { + private _orderedForEach(model: ITextModel, recursive: boolean, callback: (provider: Entry) => void): void { this._updateScores(model, recursive); @@ -191,7 +191,7 @@ export class LanguageFeatureRegistry { this._entries.sort(LanguageFeatureRegistry._compareByScoreAndTime); } - private static _compareByScoreAndTime(a: Entry, b: Entry): number { + private static _compareByScoreAndTime(a: Entry, b: Entry): number { if (a._score < b._score) { return 1; } else if (a._score > b._score) { diff --git a/src/vs/editor/common/languages/languageConfiguration.ts b/src/vs/editor/common/languages/languageConfiguration.ts index b6aa902f78a..c569efd68c1 100644 --- a/src/vs/editor/common/languages/languageConfiguration.ts +++ b/src/vs/editor/common/languages/languageConfiguration.ts @@ -7,14 +7,30 @@ import { CharCode } from '../../../base/common/charCode.js'; import { StandardTokenType } from '../encodedTokenAttributes.js'; import { ScopedLineTokens } from './supports.js'; +/** + * Configuration for line comments. + */ +export interface LineCommentConfig { + /** + * The line comment token, like `//` + */ + comment: string; + /** + * Whether the comment token should not be indented and placed at the first column. + * Defaults to false. + */ + noIndent?: boolean; +} + /** * Describes how comments for a language work. */ export interface CommentRule { /** - * The line comment token, like `// this is a comment` + * The line comment token, like `// this is a comment`. + * Can be a string or an object with comment and optional noIndent properties. */ - lineComment?: string | null; + lineComment?: string | LineCommentConfig | null; /** * The block comment character pair, like `/* block comment */` */ diff --git a/src/vs/editor/common/languages/languageConfigurationRegistry.ts b/src/vs/editor/common/languages/languageConfigurationRegistry.ts index f42b06d0b74..a545b571a8e 100644 --- a/src/vs/editor/common/languages/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/languages/languageConfigurationRegistry.ts @@ -27,6 +27,7 @@ import { LanguageBracketsConfiguration } from './supports/languageBracketsConfig */ export interface ICommentsConfiguration { lineCommentToken?: string; + lineCommentNoIndent?: boolean; blockCommentStartToken?: string; blockCommentEndToken?: string; } @@ -456,7 +457,12 @@ export class ResolvedLanguageConfiguration { const comments: ICommentsConfiguration = {}; if (commentRule.lineComment) { - comments.lineCommentToken = commentRule.lineComment; + if (typeof commentRule.lineComment === 'string') { + comments.lineCommentToken = commentRule.lineComment; + } else { + comments.lineCommentToken = commentRule.lineComment.comment; + comments.lineCommentNoIndent = commentRule.lineComment.noIndent; + } } if (commentRule.blockComment) { const [blockStart, blockEnd] = commentRule.blockComment; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index ad56a01c508..e0cdf27692b 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -23,7 +23,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange import { IGuidesTextModelPart } from './textModelGuides.js'; import { ITokenizationTextModelPart } from './tokenizationTextModelPart.js'; import { UndoRedoGroup } from '../../platform/undoRedo/common/undoRedo.js'; -import { TokenArray } from './tokens/tokenArray.js'; +import { TokenArray } from './tokens/lineTokens.js'; import { IEditorModel } from './editorCommon.js'; import { TextModelEditReason } from './textModelEditReason.js'; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 9497bc5a304..ddcc455dde6 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -46,7 +46,7 @@ import { ITokenizationTextModelPart } from '../tokenizationTextModelPart.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; import { IColorTheme } from '../../../platform/theme/common/themeService.js'; import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js'; -import { TokenArray } from '../tokens/tokenArray.js'; +import { TokenArray } from '../tokens/lineTokens.js'; import { SetWithKey } from '../../../base/common/collections.js'; import { TextModelEditReason } from '../textModelEditReason.js'; diff --git a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts index 02f789bce6a..5d61d664043 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as TreeSitter from '@vscode/tree-sitter-wasm'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { toDisposable } from '../../../../../base/common/lifecycle.js'; import { StandardTokenType } from '../../../encodedTokenAttributes.js'; @@ -196,17 +195,3 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { return model.hasTokens(); } } - -export function rangesEqual(a: TreeSitter.Range, b: TreeSitter.Range) { - return (a.startPosition.row === b.startPosition.row) - && (a.startPosition.column === b.startPosition.column) - && (a.endPosition.row === b.endPosition.row) - && (a.endPosition.column === b.endPosition.column) - && (a.startIndex === b.startIndex) - && (a.endIndex === b.endIndex); -} - -export function rangesIntersect(a: TreeSitter.Range, b: TreeSitter.Range) { - return (a.startIndex <= b.startIndex && a.endIndex >= b.startIndex) || - (b.startIndex <= a.startIndex && b.endIndex >= a.startIndex); -} diff --git a/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts b/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts index 2ced0ac10d4..e60618d03d2 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/treeSitterTree.ts @@ -13,7 +13,6 @@ import { TextLength } from '../../../core/text/textLength.js'; import { IModelContentChangedEvent, IModelContentChange } from '../../../textModelEvents.js'; import { TextModel } from '../../textModel.js'; import { gotoParent, getClosestPreviousNodes, nextSiblingOrParentSibling, gotoNthChild } from './cursorUtils.js'; -import { rangesIntersect, rangesEqual } from './treeSitterSyntaxTokenBackend.js'; import { Range } from '../../../core/range.js'; export class TreeSitterTree extends Disposable { @@ -445,3 +444,16 @@ function newTimeOutProgressCallback(): (state: TreeSitter.ParseState) => void { return false; }; } +export function rangesEqual(a: TreeSitter.Range, b: TreeSitter.Range) { + return (a.startPosition.row === b.startPosition.row) + && (a.startPosition.column === b.startPosition.column) + && (a.endPosition.row === b.endPosition.row) + && (a.endPosition.column === b.endPosition.column) + && (a.startIndex === b.startIndex) + && (a.endIndex === b.endIndex); +} + +export function rangesIntersect(a: TreeSitter.Range, b: TreeSitter.Range) { + return (a.startIndex <= b.startIndex && a.endIndex >= b.startIndex) || + (b.startIndex <= a.startIndex && b.endIndex >= a.startIndex); +} diff --git a/src/vs/editor/common/services/textResourceConfiguration.ts b/src/vs/editor/common/services/textResourceConfiguration.ts index da2c0a8b653..70975287424 100644 --- a/src/vs/editor/common/services/textResourceConfiguration.ts +++ b/src/vs/editor/common/services/textResourceConfiguration.ts @@ -72,7 +72,7 @@ export interface ITextResourceConfigurationService { * @param configurationTarget Optional target into which the configuration has to be updated. * If not specified, target will be derived by checking where the configuration is defined. */ - updateValue(resource: URI | undefined, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise; + updateValue(resource: URI | undefined, key: string, value: unknown, configurationTarget?: ConfigurationTarget): Promise; } diff --git a/src/vs/editor/common/services/textResourceConfigurationService.ts b/src/vs/editor/common/services/textResourceConfigurationService.ts index 85ef41882c4..6eda008b6ef 100644 --- a/src/vs/editor/common/services/textResourceConfigurationService.ts +++ b/src/vs/editor/common/services/textResourceConfigurationService.ts @@ -30,14 +30,14 @@ export class TextResourceConfigurationService extends Disposable implements ITex getValue(resource: URI | undefined, section?: string): T; getValue(resource: URI | undefined, at?: IPosition, section?: string): T; - getValue(resource: URI | undefined, arg2?: any, arg3?: any): T { + getValue(resource: URI | undefined, arg2?: unknown, arg3?: unknown): T { if (typeof arg3 === 'string') { return this._getValue(resource, Position.isIPosition(arg2) ? arg2 : null, arg3); } return this._getValue(resource, null, typeof arg2 === 'string' ? arg2 : undefined); } - updateValue(resource: URI | undefined, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise { + updateValue(resource: URI | undefined, key: string, value: unknown, configurationTarget?: ConfigurationTarget): Promise { const language = resource ? this.getLanguage(resource, null) : null; const configurationValue = this.configurationService.inspect(key, { resource, overrideIdentifier: language }); if (configurationTarget === undefined) { @@ -47,7 +47,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex return this.configurationService.updateValue(key, value, { resource, overrideIdentifier }, configurationTarget); } - private deriveConfigurationTarget(configurationValue: IConfigurationValue, language: string | null): ConfigurationTarget { + private deriveConfigurationTarget(configurationValue: IConfigurationValue, language: string | null): ConfigurationTarget { if (language) { if (configurationValue.memory?.override !== undefined) { return ConfigurationTarget.MEMORY; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 999efdff88e..6c05046a9b2 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -188,150 +188,153 @@ export enum EditorOption { autoClosingOvertype = 11, autoClosingQuotes = 12, autoIndent = 13, - automaticLayout = 14, - autoSurround = 15, - bracketPairColorization = 16, - guides = 17, - codeLens = 18, - codeLensFontFamily = 19, - codeLensFontSize = 20, - colorDecorators = 21, - colorDecoratorsLimit = 22, - columnSelection = 23, - comments = 24, - contextmenu = 25, - copyWithSyntaxHighlighting = 26, - cursorBlinking = 27, - cursorSmoothCaretAnimation = 28, - cursorStyle = 29, - cursorSurroundingLines = 30, - cursorSurroundingLinesStyle = 31, - cursorWidth = 32, - disableLayerHinting = 33, - disableMonospaceOptimizations = 34, - domReadOnly = 35, - dragAndDrop = 36, - dropIntoEditor = 37, - experimentalEditContextEnabled = 38, - emptySelectionClipboard = 39, - experimentalGpuAcceleration = 40, - experimentalWhitespaceRendering = 41, - extraEditorClassName = 42, - fastScrollSensitivity = 43, - find = 44, - fixedOverflowWidgets = 45, - folding = 46, - foldingStrategy = 47, - foldingHighlight = 48, - foldingImportsByDefault = 49, - foldingMaximumRegions = 50, - unfoldOnClickAfterEndOfLine = 51, - fontFamily = 52, - fontInfo = 53, - fontLigatures = 54, - fontSize = 55, - fontWeight = 56, - fontVariations = 57, - formatOnPaste = 58, - formatOnType = 59, - glyphMargin = 60, - gotoLocation = 61, - hideCursorInOverviewRuler = 62, - hover = 63, - inDiffEditor = 64, - inlineSuggest = 65, - letterSpacing = 66, - lightbulb = 67, - lineDecorationsWidth = 68, - lineHeight = 69, - lineNumbers = 70, - lineNumbersMinChars = 71, - linkedEditing = 72, - links = 73, - matchBrackets = 74, - minimap = 75, - mouseStyle = 76, - mouseWheelScrollSensitivity = 77, - mouseWheelZoom = 78, - multiCursorMergeOverlapping = 79, - multiCursorModifier = 80, - multiCursorPaste = 81, - multiCursorLimit = 82, - occurrencesHighlight = 83, - occurrencesHighlightDelay = 84, - overtypeCursorStyle = 85, - overtypeOnPaste = 86, - overviewRulerBorder = 87, - overviewRulerLanes = 88, - padding = 89, - pasteAs = 90, - parameterHints = 91, - peekWidgetDefaultFocus = 92, - placeholder = 93, - definitionLinkOpensInPeek = 94, - quickSuggestions = 95, - quickSuggestionsDelay = 96, - readOnly = 97, - readOnlyMessage = 98, - renameOnType = 99, - renderControlCharacters = 100, - renderFinalNewline = 101, - renderLineHighlight = 102, - renderLineHighlightOnlyWhenFocus = 103, - renderValidationDecorations = 104, - renderWhitespace = 105, - revealHorizontalRightPadding = 106, - roundedSelection = 107, - rulers = 108, - scrollbar = 109, - scrollBeyondLastColumn = 110, - scrollBeyondLastLine = 111, - scrollPredominantAxis = 112, - selectionClipboard = 113, - selectionHighlight = 114, - selectOnLineNumbers = 115, - showFoldingControls = 116, - showUnused = 117, - snippetSuggestions = 118, - smartSelect = 119, - smoothScrolling = 120, - stickyScroll = 121, - stickyTabStops = 122, - stopRenderingLineAfter = 123, - suggest = 124, - suggestFontSize = 125, - suggestLineHeight = 126, - suggestOnTriggerCharacters = 127, - suggestSelection = 128, - tabCompletion = 129, - tabIndex = 130, - unicodeHighlighting = 131, - unusualLineTerminators = 132, - useShadowDOM = 133, - useTabStops = 134, - wordBreak = 135, - wordSegmenterLocales = 136, - wordSeparators = 137, - wordWrap = 138, - wordWrapBreakAfterCharacters = 139, - wordWrapBreakBeforeCharacters = 140, - wordWrapColumn = 141, - wordWrapOverride1 = 142, - wordWrapOverride2 = 143, - wrappingIndent = 144, - wrappingStrategy = 145, - showDeprecated = 146, - inlayHints = 147, - effectiveCursorStyle = 148, - editorClassName = 149, - pixelRatio = 150, - tabFocusMode = 151, - layoutInfo = 152, - wrappingInfo = 153, - defaultColorDecorators = 154, - colorDecoratorsActivatedOn = 155, - inlineCompletionsAccessibilityVerbose = 156, - effectiveExperimentalEditContextEnabled = 157 + autoIndentOnPaste = 14, + autoIndentOnPasteWithinString = 15, + automaticLayout = 16, + autoSurround = 17, + bracketPairColorization = 18, + guides = 19, + codeLens = 20, + codeLensFontFamily = 21, + codeLensFontSize = 22, + colorDecorators = 23, + colorDecoratorsLimit = 24, + columnSelection = 25, + comments = 26, + contextmenu = 27, + copyWithSyntaxHighlighting = 28, + cursorBlinking = 29, + cursorSmoothCaretAnimation = 30, + cursorStyle = 31, + cursorSurroundingLines = 32, + cursorSurroundingLinesStyle = 33, + cursorWidth = 34, + disableLayerHinting = 35, + disableMonospaceOptimizations = 36, + domReadOnly = 37, + dragAndDrop = 38, + dropIntoEditor = 39, + editContext = 40, + emptySelectionClipboard = 41, + experimentalGpuAcceleration = 42, + experimentalWhitespaceRendering = 43, + extraEditorClassName = 44, + fastScrollSensitivity = 45, + find = 46, + fixedOverflowWidgets = 47, + folding = 48, + foldingStrategy = 49, + foldingHighlight = 50, + foldingImportsByDefault = 51, + foldingMaximumRegions = 52, + unfoldOnClickAfterEndOfLine = 53, + fontFamily = 54, + fontInfo = 55, + fontLigatures = 56, + fontSize = 57, + fontWeight = 58, + fontVariations = 59, + formatOnPaste = 60, + formatOnType = 61, + glyphMargin = 62, + gotoLocation = 63, + hideCursorInOverviewRuler = 64, + hover = 65, + inDiffEditor = 66, + inlineSuggest = 67, + letterSpacing = 68, + lightbulb = 69, + lineDecorationsWidth = 70, + lineHeight = 71, + lineNumbers = 72, + lineNumbersMinChars = 73, + linkedEditing = 74, + links = 75, + matchBrackets = 76, + minimap = 77, + mouseStyle = 78, + mouseWheelScrollSensitivity = 79, + mouseWheelZoom = 80, + multiCursorMergeOverlapping = 81, + multiCursorModifier = 82, + multiCursorPaste = 83, + multiCursorLimit = 84, + occurrencesHighlight = 85, + occurrencesHighlightDelay = 86, + overtypeCursorStyle = 87, + overtypeOnPaste = 88, + overviewRulerBorder = 89, + overviewRulerLanes = 90, + padding = 91, + pasteAs = 92, + parameterHints = 93, + peekWidgetDefaultFocus = 94, + placeholder = 95, + definitionLinkOpensInPeek = 96, + quickSuggestions = 97, + quickSuggestionsDelay = 98, + readOnly = 99, + readOnlyMessage = 100, + renameOnType = 101, + renderControlCharacters = 102, + renderFinalNewline = 103, + renderLineHighlight = 104, + renderLineHighlightOnlyWhenFocus = 105, + renderValidationDecorations = 106, + renderWhitespace = 107, + revealHorizontalRightPadding = 108, + roundedSelection = 109, + rulers = 110, + scrollbar = 111, + scrollBeyondLastColumn = 112, + scrollBeyondLastLine = 113, + scrollPredominantAxis = 114, + selectionClipboard = 115, + selectionHighlight = 116, + selectOnLineNumbers = 117, + showFoldingControls = 118, + showUnused = 119, + snippetSuggestions = 120, + smartSelect = 121, + smoothScrolling = 122, + stickyScroll = 123, + stickyTabStops = 124, + stopRenderingLineAfter = 125, + suggest = 126, + suggestFontSize = 127, + suggestLineHeight = 128, + suggestOnTriggerCharacters = 129, + suggestSelection = 130, + tabCompletion = 131, + tabIndex = 132, + unicodeHighlighting = 133, + unusualLineTerminators = 134, + useShadowDOM = 135, + useTabStops = 136, + wordBreak = 137, + wordSegmenterLocales = 138, + wordSeparators = 139, + wordWrap = 140, + wordWrapBreakAfterCharacters = 141, + wordWrapBreakBeforeCharacters = 142, + wordWrapColumn = 143, + wordWrapOverride1 = 144, + wordWrapOverride2 = 145, + wrappingIndent = 146, + wrappingStrategy = 147, + showDeprecated = 148, + inlayHints = 149, + effectiveCursorStyle = 150, + editorClassName = 151, + pixelRatio = 152, + tabFocusMode = 153, + layoutInfo = 154, + wrappingInfo = 155, + defaultColorDecorators = 156, + colorDecoratorsActivatedOn = 157, + inlineCompletionsAccessibilityVerbose = 158, + effectiveEditContext = 159, + scrollOnMiddleClick = 160 } /** @@ -990,4 +993,4 @@ export enum WrappingIndent { * DeepIndent => wrapped lines get +2 indentation toward the parent. */ DeepIndent = 3 -} \ No newline at end of file +} diff --git a/src/vs/editor/common/textModelEvents.ts b/src/vs/editor/common/textModelEvents.ts index 3123b120b28..8f9ab67df70 100644 --- a/src/vs/editor/common/textModelEvents.ts +++ b/src/vs/editor/common/textModelEvents.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRange } from './core/range.js'; +import { IPosition } from './core/position.js'; +import { IRange, Range } from './core/range.js'; import { Selection } from './core/selection.js'; import { IModelDecoration, InjectedTextOptions } from './model.js'; @@ -403,6 +404,24 @@ export class ModelLineHeightChangedEvent { constructor(changes: ModelLineHeightChanged[]) { this.changes = changes; } + + public affects(rangeOrPosition: IRange | IPosition) { + if (Range.isIRange(rangeOrPosition)) { + for (const change of this.changes) { + if (change.lineNumber >= rangeOrPosition.startLineNumber && change.lineNumber <= rangeOrPosition.endLineNumber) { + return true; + } + } + return false; + } else { + for (const change of this.changes) { + if (change.lineNumber === rangeOrPosition.lineNumber) { + return true; + } + } + return false; + } + } } /** diff --git a/src/vs/editor/common/tokens/lineTokens.ts b/src/vs/editor/common/tokens/lineTokens.ts index 5beed680735..ef2ec6cd642 100644 --- a/src/vs/editor/common/tokens/lineTokens.ts +++ b/src/vs/editor/common/tokens/lineTokens.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ILanguageIdCodec } from '../languages.js'; -import { FontStyle, ColorId, StandardTokenType, MetadataConsts, TokenMetadata, ITokenPresentation } from '../encodedTokenAttributes.js'; +import { FontStyle, ColorId, StandardTokenType, MetadataConsts, ITokenPresentation, TokenMetadata } from '../encodedTokenAttributes.js'; import { IPosition } from '../core/position.js'; import { ITextModel } from '../model.js'; import { OffsetRange } from '../core/ranges/offsetRange.js'; -import { TokenArray, TokenArrayBuilder } from './tokenArray.js'; import { onUnexpectedError } from '../../../base/common/errors.js'; @@ -422,3 +421,107 @@ export function getStandardTokenTypeAtPosition(model: ITextModel, position: IPos const tokenType = lineTokens.getStandardTokenType(tokenIndex); return tokenType; } + + + +/** + * This class represents a sequence of tokens. + * Conceptually, each token has a length and a metadata number. + * A token array might be used to annotate a string with metadata. + * Use {@link TokenArrayBuilder} to efficiently create a token array. + * + * TODO: Make this class more efficient (e.g. by using a Int32Array). +*/ +export class TokenArray { + public static fromLineTokens(lineTokens: LineTokens): TokenArray { + const tokenInfo: TokenInfo[] = []; + for (let i = 0; i < lineTokens.getCount(); i++) { + tokenInfo.push(new TokenInfo(lineTokens.getEndOffset(i) - lineTokens.getStartOffset(i), lineTokens.getMetadata(i))); + } + return TokenArray.create(tokenInfo); + } + + public static create(tokenInfo: TokenInfo[]): TokenArray { + return new TokenArray(tokenInfo); + } + + private constructor( + private readonly _tokenInfo: TokenInfo[] + ) { } + + public toLineTokens(lineContent: string, decoder: ILanguageIdCodec): LineTokens { + return LineTokens.createFromTextAndMetadata(this.map((r, t) => ({ text: r.substring(lineContent), metadata: t.metadata })), decoder); + } + + public forEach(cb: (range: OffsetRange, tokenInfo: TokenInfo) => void): void { + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); + cb(range, tokenInfo); + lengthSum += tokenInfo.length; + } + } + + public map(cb: (range: OffsetRange, tokenInfo: TokenInfo) => T): T[] { + const result: T[] = []; + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); + result.push(cb(range, tokenInfo)); + lengthSum += tokenInfo.length; + } + return result; + } + + public slice(range: OffsetRange): TokenArray { + const result: TokenInfo[] = []; + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const tokenStart = lengthSum; + const tokenEndEx = tokenStart + tokenInfo.length; + if (tokenEndEx > range.start) { + if (tokenStart >= range.endExclusive) { + break; + } + + const deltaBefore = Math.max(0, range.start - tokenStart); + const deltaAfter = Math.max(0, tokenEndEx - range.endExclusive); + + result.push(new TokenInfo(tokenInfo.length - deltaBefore - deltaAfter, tokenInfo.metadata)); + } + + lengthSum += tokenInfo.length; + } + return TokenArray.create(result); + } + + public append(other: TokenArray): TokenArray { + const result: TokenInfo[] = this._tokenInfo.concat(other._tokenInfo); + return TokenArray.create(result); + } +} + +export type ITokenMetadata = number; + +export class TokenInfo { + constructor( + public readonly length: number, + public readonly metadata: ITokenMetadata + ) { } +} +/** + * TODO: Make this class more efficient (e.g. by using a Int32Array). +*/ + +export class TokenArrayBuilder { + private readonly _tokens: TokenInfo[] = []; + + public add(length: number, metadata: ITokenMetadata): void { + this._tokens.push(new TokenInfo(length, metadata)); + } + + public build(): TokenArray { + return TokenArray.create(this._tokens); + } +} + diff --git a/src/vs/editor/common/tokens/tokenArray.ts b/src/vs/editor/common/tokens/tokenArray.ts deleted file mode 100644 index 93f19a8ec97..00000000000 --- a/src/vs/editor/common/tokens/tokenArray.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { OffsetRange } from '../core/ranges/offsetRange.js'; -import { ILanguageIdCodec } from '../languages.js'; -import { LineTokens } from './lineTokens.js'; - -/** - * This class represents a sequence of tokens. - * Conceptually, each token has a length and a metadata number. - * A token array might be used to annotate a string with metadata. - * Use {@link TokenArrayBuilder} to efficiently create a token array. - * - * TODO: Make this class more efficient (e.g. by using a Int32Array). -*/ -export class TokenArray { - public static fromLineTokens(lineTokens: LineTokens): TokenArray { - const tokenInfo: TokenInfo[] = []; - for (let i = 0; i < lineTokens.getCount(); i++) { - tokenInfo.push(new TokenInfo(lineTokens.getEndOffset(i) - lineTokens.getStartOffset(i), lineTokens.getMetadata(i))); - } - return TokenArray.create(tokenInfo); - } - - public static create(tokenInfo: TokenInfo[]): TokenArray { - return new TokenArray(tokenInfo); - } - - private constructor( - private readonly _tokenInfo: TokenInfo[], - ) { } - - public toLineTokens(lineContent: string, decoder: ILanguageIdCodec): LineTokens { - return LineTokens.createFromTextAndMetadata(this.map((r, t) => ({ text: r.substring(lineContent), metadata: t.metadata })), decoder); - } - - public forEach(cb: (range: OffsetRange, tokenInfo: TokenInfo) => void): void { - let lengthSum = 0; - for (const tokenInfo of this._tokenInfo) { - const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); - cb(range, tokenInfo); - lengthSum += tokenInfo.length; - } - } - - public map(cb: (range: OffsetRange, tokenInfo: TokenInfo) => T): T[] { - const result: T[] = []; - let lengthSum = 0; - for (const tokenInfo of this._tokenInfo) { - const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); - result.push(cb(range, tokenInfo)); - lengthSum += tokenInfo.length; - } - return result; - } - - public slice(range: OffsetRange): TokenArray { - const result: TokenInfo[] = []; - let lengthSum = 0; - for (const tokenInfo of this._tokenInfo) { - const tokenStart = lengthSum; - const tokenEndEx = tokenStart + tokenInfo.length; - if (tokenEndEx > range.start) { - if (tokenStart >= range.endExclusive) { - break; - } - - const deltaBefore = Math.max(0, range.start - tokenStart); - const deltaAfter = Math.max(0, tokenEndEx - range.endExclusive); - - result.push(new TokenInfo(tokenInfo.length - deltaBefore - deltaAfter, tokenInfo.metadata)); - } - - lengthSum += tokenInfo.length; - } - return TokenArray.create(result); - } - - public append(other: TokenArray): TokenArray { - const result: TokenInfo[] = this._tokenInfo.concat(other._tokenInfo); - return TokenArray.create(result); - } -} - -export type TokenMetadata = number; - -export class TokenInfo { - constructor( - public readonly length: number, - public readonly metadata: TokenMetadata, - ) { } -} - -/** - * TODO: Make this class more efficient (e.g. by using a Int32Array). -*/ -export class TokenArrayBuilder { - private readonly _tokens: TokenInfo[] = []; - - public add(length: number, metadata: TokenMetadata): void { - this._tokens.push(new TokenInfo(length, metadata)); - } - - public build(): TokenArray { - return TokenArray.create(this._tokens); - } -} diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 6a382131fce..638f39be363 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -205,7 +205,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman return true; } // TODO this is very ugly. The entire copy/paste/cut system needs a complete refactoring. - if (focusedEditor.getOption(EditorOption.effectiveExperimentalEditContextEnabled) && browserCommand === 'cut') { + if (focusedEditor.getOption(EditorOption.effectiveEditContext) && browserCommand === 'cut') { // execCommand(copy) works for edit context, but not execCommand(cut). focusedEditor.getContainerDomNode().ownerDocument.execCommand('copy'); focusedEditor.trigger(undefined, Handler.Cut, undefined); @@ -239,8 +239,8 @@ if (PasteAction) { const focusedEditor = codeEditorService.getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasModel() && focusedEditor.hasTextFocus()) { // execCommand(paste) does not work with edit context - const experimentalEditContextEnabled = focusedEditor.getOption(EditorOption.effectiveExperimentalEditContextEnabled); - if (experimentalEditContextEnabled) { + const editContextEnabled = focusedEditor.getOption(EditorOption.effectiveEditContext); + if (editContextEnabled) { const nativeEditContext = NativeEditContextRegistry.get(focusedEditor.getId()); if (nativeEditContext) { nativeEditContext.onWillPaste(); diff --git a/src/vs/editor/contrib/codelens/browser/codelensWidget.css b/src/vs/editor/contrib/codelens/browser/codelensWidget.css index adc19b13de6..a7ec91c06d9 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensWidget.css +++ b/src/vs/editor/contrib/codelens/browser/codelensWidget.css @@ -54,14 +54,8 @@ } @keyframes fadein { - 0% { - opacity: 0; - visibility: visible; - } - - 100% { - opacity: 1; - } + 0% { opacity: 0;} + 100% { opacity: 1;} } .monaco-editor .codelens-decoration.fadein { diff --git a/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts b/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts index 63a231f23ea..1359b5babf4 100644 --- a/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts +++ b/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts @@ -27,9 +27,9 @@ export class DefaultDocumentColorProvider implements DocumentColorProvider { const alpha = colorFromInfo.alpha; const color = new Color(new RGBA(Math.round(255 * colorFromInfo.red), Math.round(255 * colorFromInfo.green), Math.round(255 * colorFromInfo.blue), alpha)); - const rgb = alpha ? Color.Format.CSS.formatRGB(color) : Color.Format.CSS.formatRGBA(color); - const hsl = alpha ? Color.Format.CSS.formatHSL(color) : Color.Format.CSS.formatHSLA(color); - const hex = alpha ? Color.Format.CSS.formatHex(color) : Color.Format.CSS.formatHexA(color); + const rgb = alpha ? Color.Format.CSS.formatRGBA(color) : Color.Format.CSS.formatRGB(color); + const hsl = alpha ? Color.Format.CSS.formatHSLA(color) : Color.Format.CSS.formatHSL(color); + const hex = alpha ? Color.Format.CSS.formatHexA(color) : Color.Format.CSS.formatHex(color); const colorPresentations: IColorPresentation[] = []; colorPresentations.push({ label: rgb, textEdit: { range: range, text: rgb } }); diff --git a/src/vs/editor/contrib/comment/browser/lineCommentCommand.ts b/src/vs/editor/contrib/comment/browser/lineCommentCommand.ts index 53875399585..9b169645d6b 100644 --- a/src/vs/editor/contrib/comment/browser/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/browser/lineCommentCommand.ts @@ -112,9 +112,12 @@ export class LineCommentCommand implements ICommand { * Analyze lines and decide which lines are relevant and what the toggle should do. * Also, build up several offsets and lengths useful in the generation of editor operations. */ - public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean, languageConfigurationService: ILanguageConfigurationService): IPreflightData { + public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean, languageConfigurationService: ILanguageConfigurationService, languageId: string): IPreflightData { let onlyWhitespaceLines = true; + const config = languageConfigurationService.getLanguageConfiguration(languageId).comments; + const lineCommentNoIndent = config?.lineCommentNoIndent ?? false; + let shouldRemoveComments: boolean; if (type === Type.Toggle) { shouldRemoveComments = true; @@ -140,15 +143,16 @@ export class LineCommentCommand implements ICommand { if (lineContentStartOffset === -1) { // Empty or whitespace only line lineData.ignore = ignoreEmptyLines; - lineData.commentStrOffset = lineContent.length; + lineData.commentStrOffset = lineCommentNoIndent ? 0 : lineContent.length; continue; } onlyWhitespaceLines = false; + const offset = lineCommentNoIndent ? 0 : lineContentStartOffset; lineData.ignore = false; - lineData.commentStrOffset = lineContentStartOffset; + lineData.commentStrOffset = offset; - if (shouldRemoveComments && !BlockCommentCommand._haystackHasNeedleAtOffset(lineContent, lineData.commentStr, lineContentStartOffset)) { + if (shouldRemoveComments && !BlockCommentCommand._haystackHasNeedleAtOffset(lineContent, lineData.commentStr, offset)) { if (type === Type.Toggle) { // Every line so far has been a line comment, but this one is not shouldRemoveComments = false; @@ -190,13 +194,14 @@ export class LineCommentCommand implements ICommand { */ public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean, languageConfigurationService: ILanguageConfigurationService): IPreflightData { const lines = LineCommentCommand._gatherPreflightCommentStrings(model, startLineNumber, endLineNumber, languageConfigurationService); + const languageId = model.getLanguageIdAtPosition(startLineNumber, 1); if (lines === null) { return { supported: false }; } - return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines, ignoreFirstLine, languageConfigurationService); + return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines, ignoreFirstLine, languageConfigurationService, languageId); } /** diff --git a/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts index e738c1d865d..0d8bd9f4151 100644 --- a/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts @@ -48,6 +48,11 @@ suite('Editor Contrib - Line Comment Command', () => { (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.ForceAdd, true, true) ); + const testLineCommentCommandTokenFirstColumn = createTestCommandHelper( + { lineComment: { comment: '!@#', noIndent: true }, blockComment: [''] }, + (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.Toggle, true, true) + ); + test('comment single line', function () { testLineCommentCommand( [ @@ -81,6 +86,21 @@ suite('Editor Contrib - Line Comment Command', () => { ); }); + test('comment with token column fixed', function () { + testLineCommentCommandTokenFirstColumn( + [ + 'some text', + '\tsome more text' + ], + new Selection(2, 1, 2, 1), + [ + 'some text', + '!@# \tsome more text' + ], + new Selection(2, 5, 2, 5) + ); + }); + function createSimpleModel(lines: string[]): ISimpleModel { return { getLineContent: (lineNumber: number) => { @@ -110,7 +130,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' ', ' c', '\t\td' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, disposable.add(new TestLanguageConfigurationService())); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, disposable.add(new TestLanguageConfigurationService()), 'plaintext'); if (!r.supported) { throw new Error(`unexpected`); } @@ -141,7 +161,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' rem ', ' !@# c', '\t\t!@#d' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, disposable.add(new TestLanguageConfigurationService())); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, disposable.add(new TestLanguageConfigurationService()), 'plaintext'); if (!r.supported) { throw new Error(`unexpected`); } diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index e64fb54d32d..97d96df256f 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -38,7 +38,7 @@ import { registerIcon, widgetClose } from '../../../../platform/theme/common/ico import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { isHighContrast } from '../../../../platform/theme/common/theme.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { defaultInputBoxStyles, defaultToggleStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { Selection } from '../../../common/core/selection.js'; import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; @@ -1008,7 +1008,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL icon: findPreviousMatchIcon, hoverDelegate, onTrigger: () => { - assertIsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError); + assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError); } }, this._hoverService)); @@ -1018,7 +1018,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL icon: findNextMatchIcon, hoverDelegate, onTrigger: () => { - assertIsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError); + assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError); } }, this._hoverService)); diff --git a/src/vs/editor/contrib/gpu/browser/gpuActions.ts b/src/vs/editor/contrib/gpu/browser/gpuActions.ts index a1a3afe9a5b..a323d67b1fc 100644 --- a/src/vs/editor/contrib/gpu/browser/gpuActions.ts +++ b/src/vs/editor/contrib/gpu/browser/gpuActions.ts @@ -105,7 +105,7 @@ class DebugEditorGpuRendererAction extends EditorAction { const atlas = ViewGpuContext.atlas; const fontFamily = configurationService.getValue('editor.fontFamily'); const fontSize = configurationService.getValue('editor.fontSize'); - const rasterizer = new GlyphRasterizer(fontSize, fontFamily, getActiveWindow().devicePixelRatio); + const rasterizer = new GlyphRasterizer(fontSize, fontFamily, getActiveWindow().devicePixelRatio, ViewGpuContext.decorationStyleCache); let chars = await quickInputService.input({ prompt: 'Enter a character to draw (prefix with 0x for code point))' }); diff --git a/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts index 986e464be99..2cc8ce5757f 100644 --- a/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts @@ -71,7 +71,11 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant range.endLineNumber) { - return; - } - let firstLineText = model.getLineContent(startLineNumber); if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) { const indentOfFirstLine = getGoodIndentForLine(autoIndent, model, model.getLanguageId(), startLineNumber, indentConverter, this._languageConfigurationService); @@ -571,23 +559,6 @@ export class AutoIndentOnPaste implements IEditorContribution { return containsOnlyWhitespace; } - private shouldIgnoreLine(model: ITextModel, lineNumber: number): boolean { - model.tokenization.forceTokenization(lineNumber); - const nonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); - if (nonWhitespaceColumn === 0) { - return true; - } - const tokens = model.tokenization.getLineTokens(lineNumber); - if (tokens.getCount() > 0) { - const firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhitespaceColumn); - if (firstNonWhitespaceTokenIndex >= 0 && tokens.getStandardTokenType(firstNonWhitespaceTokenIndex) === StandardTokenType.Comment) { - return true; - } - } - - return false; - } - public dispose(): void { this.callOnDispose.dispose(); this.callOnModel.dispose(); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/animation.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/animation.ts index 7b5ed388002..0f50d23385c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/animation.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/animation.ts @@ -79,14 +79,14 @@ export class ObservableAnimatedValue { getValue(reader: IReader | undefined): number { const value = this._value.read(reader); if (!value.isFinished()) { - Scheduler.instance.invalidateOnNextAnimationFrame(reader); + AnimationFrameScheduler.instance.invalidateOnNextAnimationFrame(reader); } return value.getValue(); } } -class Scheduler { - public static instance = new Scheduler(); +export class AnimationFrameScheduler { + public static instance = new AnimationFrameScheduler(); private readonly _counter = observableSignal(this); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts index a9f6b247849..9dee6faeb9c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts @@ -55,7 +55,7 @@ export class TextModelChangeRecorder extends Disposable { sourceId: 'TextModel.setChangeReason', source: source, time: Date.now(), - modelUri: tm.uri.toString(), + modelUri: tm.uri, modelVersion: tm.getVersionId(), }; setTimeout(() => { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index cdbec41332f..94f571b4a78 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -784,72 +784,72 @@ export class InlineCompletionsModel extends Disposable { return; } - completion.reportEndOfLife({ kind: InlineCompletionEndOfLifeReasonKind.Accepted }); + // Make sure the completion list will not be disposed before the text change is sent. + completion.addRef(); - if (completion.command) { - // Make sure the completion list will not be disposed. - completion.source.addRef(); - } + try { + editor.pushUndoStop(); + if (completion.snippetInfo) { + TextModelEditReason.editWithReason(this._getMetadata(completion), () => { + editor.executeEdits( + 'inlineSuggestion.accept', + [ + EditOperation.replace(completion.editRange, ''), + ...completion.additionalTextEdits + ] + ); + }); + editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); + SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); + } else { + const edits = state.edits; - editor.pushUndoStop(); - if (completion.snippetInfo) { - TextModelEditReason.editWithReason(this._getMetadata(completion), () => { - editor.executeEdits( - 'inlineSuggestion.accept', - [ - EditOperation.replace(completion.editRange, ''), + // The cursor should move to the end of the edit, not the end of the range provided by the extension + // Inline Edit diffs (human readable) the suggestion from the extension so it already removes common suffix/prefix + // Inline Completions does diff the suggestion so it may contain common suffix + let minimalEdits = edits; + if (state.kind === 'ghostText') { + minimalEdits = removeTextReplacementCommonSuffixPrefix(edits, this.textModel); + } + const selections = getEndPositionsAfterApplying(minimalEdits).map(p => Selection.fromPositions(p)); + + TextModelEditReason.editWithReason(this._getMetadata(completion), () => { + editor.executeEdits('inlineSuggestion.accept', [ + ...edits.map(edit => EditOperation.replace(edit.range, edit.text)), ...completion.additionalTextEdits - ] - ); - }); - editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); - SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); - } else { - const edits = state.edits; + ]); + }); + if (completion.displayLocation === undefined) { + // do not move the cursor when the completion is displayed in a different location + editor.setSelections(state.kind === 'inlineEdit' ? selections.slice(-1) : selections, 'inlineCompletionAccept'); + } - // The cursor should move to the end of the edit, not the end of the range provided by the extension - // Inline Edit diffs (human readable) the suggestion from the extension so it already removes common suffix/prefix - // Inline Completions does diff the suggestion so it may contain common suffix - let minimalEdits = edits; - if (state.kind === 'ghostText') { - minimalEdits = removeTextReplacementCommonSuffixPrefix(edits, this.textModel); - } - const selections = getEndPositionsAfterApplying(minimalEdits).map(p => Selection.fromPositions(p)); - - TextModelEditReason.editWithReason(this._getMetadata(completion), () => { - editor.executeEdits('inlineSuggestion.accept', [ - ...edits.map(edit => EditOperation.replace(edit.range, edit.text)), - ...completion.additionalTextEdits - ]); - }); - if (completion.displayLocation === undefined) { - // do not move the cursor when the completion is displayed in a different location - editor.setSelections(state.kind === 'inlineEdit' ? selections.slice(-1) : selections, 'inlineCompletionAccept'); + if (state.kind === 'inlineEdit' && !this._accessibilityService.isMotionReduced()) { + // we can assume that edits is sorted! + const editRanges = new TextEdit(edits).getNewRanges(); + const dec = this._store.add(new FadeoutDecoration(editor, editRanges, () => { + this._store.delete(dec); + })); + } } - if (state.kind === 'inlineEdit' && !this._accessibilityService.isMotionReduced()) { - // we can assume that edits is sorted! - const editRanges = new TextEdit(edits).getNewRanges(); - const dec = this._store.add(new FadeoutDecoration(editor, editRanges, () => { - this._store.delete(dec); - })); + this._onDidAccept.fire(); + + // Reset before invoking the command, as the command might cause a follow up trigger (which we don't want to reset). + this.stop(); + + if (completion.command) { + await this._commandService + .executeCommand(completion.command.id, ...(completion.command.arguments || [])) + .then(undefined, onUnexpectedExternalError); } + + completion.reportEndOfLife({ kind: InlineCompletionEndOfLifeReasonKind.Accepted }); + } finally { + completion.removeRef(); + this._inAcceptFlow.set(true, undefined); + this._lastAcceptedInlineCompletionInfo = { textModelVersionIdAfter: this.textModel.getVersionId(), inlineCompletion: completion }; } - - this._onDidAccept.fire(); - - // Reset before invoking the command, as the command might cause a follow up trigger (which we don't want to reset). - this.stop(); - - if (completion.command) { - await this._commandService - .executeCommand(completion.command.id, ...(completion.command.arguments || [])) - .then(undefined, onUnexpectedExternalError); - completion.source.removeRef(); - } - - this._inAcceptFlow.set(true, undefined); - this._lastAcceptedInlineCompletionInfo = { textModelVersionIdAfter: this.textModel.getVersionId(), inlineCompletion: completion }; } public async acceptNextWord(): Promise { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 499d0e0122d..5e9195b4794 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -57,7 +57,7 @@ export class InlineCompletionsSource extends Disposable { this._loggingEnabled = observableConfigValue('editor.inlineSuggest.logFetch', false, this._configurationService).recomputeInitiallyAndOnChange(this._store); this._structuredFetchLogger = this._register(this._instantiationService.createInstance(StructuredLogger.cast< { kind: 'start'; requestId: number; context: unknown } & IRecordableEditorLogEntry - | { kind: 'end'; error: any; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry + | { kind: 'end'; error: unknown; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry >(), 'editor.inlineSuggest.logFetch.commandId' )); @@ -105,7 +105,7 @@ export class InlineCompletionsSource extends Disposable { private _log(entry: { sourceId: string; kind: 'start'; requestId: number; context: unknown } & IRecordableEditorLogEntry - | { sourceId: string; kind: 'end'; error: any; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry + | { sourceId: string; kind: 'end'; error: unknown; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry ) { if (this._loggingEnabled.get()) { this._logService.info(formatRecordableLogEntry(entry)); @@ -154,12 +154,12 @@ export class InlineCompletionsSource extends Disposable { const requestId = InlineCompletionsSource._requestId++; if (this._loggingEnabled.get() || this._structuredFetchLogger.isEnabled.get()) { - this._log({ sourceId: 'InlineCompletions.fetch', kind: 'start', requestId, modelUri: this._textModel.uri.toString(), modelVersion: this._textModel.getVersionId(), context: { triggerKind: context.triggerKind }, time: Date.now() }); + this._log({ sourceId: 'InlineCompletions.fetch', kind: 'start', requestId, modelUri: this._textModel.uri, modelVersion: this._textModel.getVersionId(), context: { triggerKind: context.triggerKind }, time: Date.now() }); } const startTime = new Date(); let providerResult: InlineCompletionProviderResult | undefined = undefined; - let error: any = undefined; + let error: unknown = undefined; try { providerResult = await provideInlineCompletions( providers, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts b/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts index e6bacf3486a..f7b79676b70 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts @@ -5,6 +5,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { IObservable, observableFromEvent } from '../../../../base/common/observable.js'; +import { URI } from '../../../../base/common/uri.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -14,7 +15,7 @@ export interface IRecordableLogEntry { } export interface IRecordableEditorLogEntry extends IRecordableLogEntry { - modelUri: string; + modelUri: URI; // This has to be a URI, so that it gets translated automatically in remote scenarios modelVersion: number; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts index 0aca2fe89e5..f487e2a5bcd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createStyleSheetFromObservable } from '../../../../../base/browser/dom.js'; +import { createStyleSheetFromObservable } from '../../../../../base/browser/domStylesheets.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { derived, mapObservableArrayCached, derivedDisposable, constObservable, derivedObservableWithCache, IObservable, ISettableObservable } from '../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts index b43d49953be..6197b67ae97 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts @@ -197,7 +197,7 @@ export class InlineEditsGutterIndicator extends Disposable { const layout = this._editorObs.layoutInfo.read(reader); - const lineHeight = this._editorObs.getOption(EditorOption.lineHeight).read(reader); + const lineHeight = this._editorObs.observeLineHeightForLine(s.range.map(r => r.startLineNumber)).read(reader); const gutterViewPortPadding = 1; // Entire gutter view from top left to bottom right diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts index cd19acc154f..de4b6963ab7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SingleLineEdit } from '../../../../../common/core/edits/lineEdit.js'; +import { LineReplacement } from '../../../../../common/core/edits/lineEdit.js'; import { LineRange } from '../../../../../common/core/ranges/lineRange.js'; import { Position } from '../../../../../common/core/position.js'; import { TextEdit } from '../../../../../common/core/edits/textEdit.js'; @@ -13,7 +13,7 @@ import { InlineSuggestionItem } from '../../model/inlineSuggestionItem.js'; export class InlineEditWithChanges { public get lineEdit() { - return SingleLineEdit.fromSingleTextEdit(this.edit.toReplacement(this.originalText), this.originalText); + return LineReplacement.fromSingleTextEdit(this.edit.toReplacement(this.originalText), this.originalText); } public get originalLineRange() { return this.lineEdit.lineRange; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts index 9a58349ef2f..78ed1ad3988 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts @@ -12,14 +12,13 @@ import { asCssVariable } from '../../../../../../../platform/theme/common/colorU import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js'; import { ICodeEditor } from '../../../../../../browser/editorBrowser.js'; import { ObservableCodeEditor, observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; import { InlineCompletionDisplayLocation } from '../../../../../../common/languages.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getEditorBlendedColor, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorsuccessfulBackground } from '../theme.js'; import { maxContentWidthInRange, rectToProps } from '../utils/utils.js'; @@ -137,7 +136,7 @@ export class InlineEditsCustomView extends Disposable implements IInlineEditsVie const { lineWidth, lineWidthBelow, lineWidthAbove, startContentLeftOffset, endContentLeftOffset } = contentState.read(reader); const contentLeft = this._editorObs.layoutInfoContentLeft.read(reader); - const lineHeight = this._editorObs.getOption(EditorOption.lineHeight).read(reader); + const lineHeight = this._editorObs.observeLineHeightForLine(startLineNumber).read(reader); const scrollTop = this._editorObs.scrollTop.read(reader); const scrollLeft = this._editorObs.scrollLeft.read(reader); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts index fb3eec679d3..5eb8cb5f67d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts @@ -12,16 +12,14 @@ import { editorBackground } from '../../../../../../../platform/theme/common/col import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; import { ICodeEditor } from '../../../../../../browser/editorBrowser.js'; import { ObservableCodeEditor, observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; -import { EditorOption } from '../../../../../../common/config/editorOptions.js'; -import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; -import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; import { Position } from '../../../../../../common/core/position.js'; import { Range } from '../../../../../../common/core/range.js'; +import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; +import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../../common/viewModel.js'; import { GhostText, GhostTextPart } from '../../../model/ghostText.js'; import { GhostTextView } from '../../ghostText/ghostTextView.js'; @@ -55,13 +53,14 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits }); private readonly _trimVertically = derived(this, reader => { - const text = this._state.read(reader)?.text; + const state = this._state.read(reader); + const text = state?.text; if (!text || text.trim() === '') { return { topOffset: 0, bottomOffset: 0, linesTop: 0, linesBottom: 0 }; } // Adjust for leading/trailing newlines - const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const lineHeight = this._editor.getLineHeightForPosition(new Position(state.lineNumber, 1)); const eol = this._editor.getModel()!.getEOL(); let linesTop = 0; let linesBottom = 0; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts index 27447f6a1ac..060be8a3543 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts @@ -14,17 +14,16 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS import { IEditorMouseEvent, IViewZoneChangeAccessor } from '../../../../../../browser/editorBrowser.js'; import { EditorMouseEvent } from '../../../../../../browser/editorDom.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Point } from '../../../../../../common/core/2d/point.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; +import { Point } from '../../../../../../common/core/2d/point.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; +import { Range } from '../../../../../../common/core/range.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; -import { Range } from '../../../../../../common/core/range.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; import { IModelDecorationOptions, TrackedRangeStickiness } from '../../../../../../common/model.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../../common/viewModel.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getEditorBlendedColor, getModifiedBorderColor, getOriginalBorderColor, modifiedChangedLineBackgroundColor, originalBackgroundColor } from '../theme.js'; @@ -132,7 +131,15 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin const { prefixLeftOffset } = maxPrefixTrim; const { requiredWidth } = modifiedLines; - const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); + const originalLineHeights = this._editor.observeLineHeightsForLineRange(edit.originalRange).read(reader); + const modifiedLineHeights = (() => { + const lineHeights = originalLineHeights.slice(0, edit.modifiedRange.length); + while (lineHeights.length < edit.modifiedRange.length) { + lineHeights.push(originalLineHeights[originalLineHeights.length - 1]); + } + return lineHeights; + })(); + const contentLeft = this._editor.layoutInfoContentLeft.read(reader); const verticalScrollbarWidth = this._editor.layoutInfoVerticalScrollbarWidth.read(reader); const scrollLeft = this._editor.scrollLeft.read(reader); @@ -160,7 +167,7 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin originalLinesOverlay.left, originalLinesOverlay.bottom, originalLinesOverlay.width, - edit.modifiedRange.length * lineHeight + modifiedLineHeights.reduce((sum, h) => sum + h, 0) ); const background = Rect.hull([originalLinesOverlay, modifiedLinesOverlay]); @@ -173,6 +180,7 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin background, lowerBackground, lowerText, + modifiedLineHeights, minContentWidthRequired: prefixLeftOffset + maxLineWidth + verticalScrollbarWidth, }; }); @@ -205,10 +213,9 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin const layoutProps = layout.read(reader); const contentLeft = this._editor.layoutInfoContentLeft.read(reader); - const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); - modifiedLineElements.lines.forEach(l => { + modifiedLineElements.lines.forEach((l, i) => { l.style.width = `${layoutProps.lowerText.width}px`; - l.style.height = `${lineHeight}px`; + l.style.height = `${layoutProps.modifiedLineHeights[i]}px`; l.style.position = 'relative'; }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts index 592be28800f..1585373421d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts @@ -16,7 +16,6 @@ import { ICodeEditor } from '../../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; import { Rect } from '../../../../../../common/core/2d/rect.js'; import { EmbeddedCodeEditorWidget } from '../../../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; -import { EditorOption } from '../../../../../../common/config/editorOptions.js'; import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { Position } from '../../../../../../common/core/position.js'; import { Range } from '../../../../../../common/core/range.js'; @@ -289,7 +288,8 @@ export class InlineEditsSideBySideView extends Disposable implements IInlineEdit codeRect = codeRect.withMargin(VERTICAL_PADDING, HORIZONTAL_PADDING); } - const editHeight = this._editor.getOption(EditorOption.lineHeight) * inlineEdit.modifiedLineRange.length; + const previewLineHeights = this._previewEditorObs.observeLineHeightsForLineRange(inlineEdit.modifiedLineRange).read(reader); + const editHeight = previewLineHeights.reduce((acc, h) => acc + h, 0); const codeHeight = selectionBottom - selectionTop; const previewEditorHeight = Math.max(codeHeight, editHeight); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts index f30d563df5c..cf662a612be 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts @@ -48,7 +48,7 @@ export class InlineEditsWordInsertView extends Disposable implements IInlineEdit return undefined; } const contentLeft = this._editor.layoutInfoContentLeft.read(reader); - const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); + const lineHeight = this._editor.observeLineHeightForPosition(this._edit.range.getStartPosition()).read(reader); const w = this._editor.getOption(EditorOption.fontInfo).read(reader).typicalHalfwidthCharacterWidth; const width = this._edit.text.length * w + 5; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts index 09e0f708ed4..97f90697f6e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts @@ -11,16 +11,15 @@ import { constObservable, derived, IObservable, observableValue } from '../../.. import { editorBackground, editorHoverForeground } from '../../../../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; -import { Point } from '../../../../../../common/core/2d/point.js'; -import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineSource, renderLines, RenderOptions } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; +import { Point } from '../../../../../../common/core/2d/point.js'; +import { Rect } from '../../../../../../common/core/2d/rect.js'; import { StringReplacement } from '../../../../../../common/core/edits/stringEdit.js'; -import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { TextReplacement } from '../../../../../../common/core/edits/textEdit.js'; +import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; -import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; -import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; +import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getModifiedBorderColor, getOriginalBorderColor, modifiedChangedTextOverlayColor, originalChangedTextOverlayColor } from '../theme.js'; import { getEditorValidOverlayRect, mapOutFalsy, rectToProps } from '../utils/utils.js'; @@ -72,6 +71,7 @@ export class InlineEditsWordReplacementView extends Disposable implements IInlin const res = renderLines(new LineSource([tokens]), RenderOptions.fromEditor(this._editor.editor).withSetWidth(false).withScrollBeyondLastColumn(0), [], this._line, true); this._line.style.width = `${res.minWidthInPx}px`; }); + const modifiedLineHeight = this._editor.observeLineHeightForPosition(this._edit.range.getStartPosition()); this._layout = derived(this, reader => { this._renderTextEffect.read(reader); const widgetStart = this._start.read(reader); @@ -82,7 +82,7 @@ export class InlineEditsWordReplacementView extends Disposable implements IInlin return undefined; } - const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); + const lineHeight = modifiedLineHeight.read(reader); const scrollLeft = this._editor.scrollLeft.read(reader); const w = this._editor.getOption(EditorOption.fontInfo).read(reader).typicalHalfwidthCharacterWidth; diff --git a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/index.ts b/src/vs/editor/contrib/middleScroll/browser/middleScroll.contribution.ts similarity index 52% rename from src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/index.ts rename to src/vs/editor/contrib/middleScroll/browser/middleScroll.contribution.ts index 5518e6ee0c5..f82b1e3096f 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/index.ts +++ b/src/vs/editor/contrib/middleScroll/browser/middleScroll.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export { PartialFrontMatterRecord } from './frontMatterRecord.js'; -export { PartialFrontMatterRecordName } from './frontMatterRecordName.js'; -export { PartialFrontMatterRecordNameWithDelimiter } from './frontMatterRecordNameWithDelimiter.js'; +import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js'; +import { MiddleScrollController } from './middleScrollController.js'; + +registerEditorContribution(MiddleScrollController.ID, MiddleScrollController, EditorContributionInstantiation.BeforeFirstInteraction); diff --git a/src/vs/editor/contrib/middleScroll/browser/middleScroll.css b/src/vs/editor/contrib/middleScroll/browser/middleScroll.css new file mode 100644 index 00000000000..2e68b82d4de --- /dev/null +++ b/src/vs/editor/contrib/middleScroll/browser/middleScroll.css @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor { + .scroll-editor-on-middle-click-dot { + cursor: all-scroll; + position: absolute; + z-index: 1; + background-color: var(--vscode-editor-foreground, white); + border: 1px solid var(--vscode-editor-background, black); + opacity: 0.5; + width: 5px; + height: 5px; + border-radius: 50%; + transform: translate(-50%, -50%); + + &.hidden { + display: none; + } + } + + &.scroll-editor-on-middle-click-editor * { + cursor: all-scroll; + } +} diff --git a/src/vs/editor/contrib/middleScroll/browser/middleScrollController.ts b/src/vs/editor/contrib/middleScroll/browser/middleScrollController.ts new file mode 100644 index 00000000000..c885a89d209 --- /dev/null +++ b/src/vs/editor/contrib/middleScroll/browser/middleScrollController.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getWindow, addDisposableListener, n } from '../../../../base/browser/dom.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { ICodeEditor } from '../../../browser/editorBrowser.js'; +import { IEditorContribution, INewScrollPosition } from '../../../common/editorCommon.js'; +import { EditorOption } from '../../../common/config/editorOptions.js'; +import { autorun, derived, disposableObservableValue, IObservable, observableValue } from '../../../../base/common/observable.js'; +import { observableCodeEditor } from '../../../browser/observableCodeEditor.js'; +import { Point } from '../../../common/core/2d/point.js'; +import { AnimationFrameScheduler } from '../../inlineCompletions/browser/model/animation.js'; +import { appendRemoveOnDispose } from '../../../browser/widget/diffEditor/utils.js'; +import './middleScroll.css'; + +export class MiddleScrollController extends Disposable implements IEditorContribution { + public static readonly ID = 'editor.contrib.middleScroll'; + + static get(editor: ICodeEditor): MiddleScrollController | null { + return editor.getContribution(MiddleScrollController.ID); + } + + constructor( + private readonly _editor: ICodeEditor + ) { + super(); + + const obsEditor = observableCodeEditor(this._editor); + const scrollOnMiddleClick = obsEditor.getOption(EditorOption.scrollOnMiddleClick); + + this._register(autorun(reader => { + if (!scrollOnMiddleClick.read(reader)) { + return; + } + const editorDomNode = obsEditor.domNode.read(reader); + if (!editorDomNode) { + return; + } + + const scrollingSession = reader.store.add( + disposableObservableValue( + 'scrollingSession', + undefined as undefined | { mouseDeltaAfterThreshold: IObservable; initialMousePosInEditor: Point; didScroll: boolean } & IDisposable + ) + ); + + reader.store.add(this._editor.onMouseDown(e => { + const session = scrollingSession.get(); + if (session) { + scrollingSession.set(undefined, undefined); + return; + } + + if (!e.event.middleButton) { + return; + } + e.event.stopPropagation(); + e.event.preventDefault(); + + const store = new DisposableStore(); + const initialPos = new Point(e.event.posx, e.event.posy); + const mousePos = observeWindowMousePos(getWindow(editorDomNode), initialPos, store); + const mouseDeltaAfterThreshold = mousePos.map(v => v.subtract(initialPos).withThreshold(5)); + + const editorDomNodeRect = editorDomNode.getBoundingClientRect(); + const initialMousePosInEditor = new Point(initialPos.x - editorDomNodeRect.left, initialPos.y - editorDomNodeRect.top); + + scrollingSession.set({ + mouseDeltaAfterThreshold, + initialMousePosInEditor, + didScroll: false, + dispose: () => store.dispose(), + }, undefined); + + store.add(this._editor.onMouseUp(e => { + const session = scrollingSession.get(); + if (session && session.didScroll) { + // Only cancel session on release if the user scrolled during it + scrollingSession.set(undefined, undefined); + } + })); + + store.add(this._editor.onKeyDown(e => { + scrollingSession.set(undefined, undefined); + })); + })); + + reader.store.add(autorun(reader => { + const session = scrollingSession.read(reader); + if (!session) { + return; + } + + let lastTime = Date.now(); + reader.store.add(autorun(reader => { + AnimationFrameScheduler.instance.invalidateOnNextAnimationFrame(reader); + + const curTime = Date.now(); + const frameDurationMs = curTime - lastTime; + lastTime = curTime; + + const mouseDelta = session.mouseDeltaAfterThreshold.get(); + + // scroll by mouse delta every 32ms + const factor = frameDurationMs / 32; + const scrollDelta = mouseDelta.scale(factor); + + const scrollPos = new Point(this._editor.getScrollLeft(), this._editor.getScrollTop()); + this._editor.setScrollPosition(toScrollPosition(scrollPos.add(scrollDelta))); + if (!scrollDelta.isZero()) { + session.didScroll = true; + } + })); + + const directionAttr = derived(reader => { + const delta = session.mouseDeltaAfterThreshold.read(reader); + let direction: string = ''; + direction += (delta.y < 0 ? 'n' : (delta.y > 0 ? 's' : '')); + direction += (delta.x < 0 ? 'w' : (delta.x > 0 ? 'e' : '')); + return direction; + }); + reader.store.add(autorun(reader => { + editorDomNode.setAttribute('data-scroll-direction', directionAttr.read(reader)); + })); + })); + + const dotDomElem = reader.store.add(n.div({ + class: ['scroll-editor-on-middle-click-dot', scrollingSession.map(session => session ? '' : 'hidden')], + style: { + left: scrollingSession.map((session) => session ? session.initialMousePosInEditor.x : 0), + top: scrollingSession.map((session) => session ? session.initialMousePosInEditor.y : 0), + } + }).toDisposableLiveElement()); + reader.store.add(appendRemoveOnDispose(editorDomNode, dotDomElem.element)); + + reader.store.add(autorun(reader => { + const session = scrollingSession.read(reader); + editorDomNode.classList.toggle('scroll-editor-on-middle-click-editor', !!session); + })); + })); + } +} + +function observeWindowMousePos(window: Window, initialPos: Point, store: DisposableStore): IObservable { + const val = observableValue('pos', initialPos); + store.add(addDisposableListener(window, 'mousemove', (e: MouseEvent) => { + val.set(new Point(e.pageX, e.pageY), undefined); + })); + return val; +} + +function toScrollPosition(p: Point): INewScrollPosition { + return { + scrollLeft: p.x, + scrollTop: p.y, + }; +} diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index ef3cd7d78cc..7aa3fac2c6e 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -11,7 +11,7 @@ import { Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import './parameterHints.css'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../browser/editorBrowser.js'; import { EDITOR_FONT_DEFAULTS, EditorOption } from '../../../common/config/editorOptions.js'; @@ -282,16 +282,16 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { } private hasDocs(signature: languages.SignatureInformation, activeParameter: languages.ParameterInformation | undefined): boolean { - if (activeParameter && typeof activeParameter.documentation === 'string' && assertIsDefined(activeParameter.documentation).length > 0) { + if (activeParameter && typeof activeParameter.documentation === 'string' && assertReturnsDefined(activeParameter.documentation).length > 0) { return true; } - if (activeParameter && typeof activeParameter.documentation === 'object' && assertIsDefined(activeParameter.documentation).value.length > 0) { + if (activeParameter && typeof activeParameter.documentation === 'object' && assertReturnsDefined(activeParameter.documentation).value.length > 0) { return true; } - if (signature.documentation && typeof signature.documentation === 'string' && assertIsDefined(signature.documentation).length > 0) { + if (signature.documentation && typeof signature.documentation === 'string' && assertReturnsDefined(signature.documentation).length > 0) { return true; } - if (signature.documentation && typeof signature.documentation === 'object' && assertIsDefined(signature.documentation.value).length > 0) { + if (signature.documentation && typeof signature.documentation === 'object' && assertReturnsDefined(signature.documentation.value).length > 0) { return true; } return false; diff --git a/src/vs/editor/contrib/peekView/browser/peekView.ts b/src/vs/editor/contrib/peekView/browser/peekView.ts index ce8921b550f..fe9cffb401e 100644 --- a/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -289,6 +289,7 @@ export const peekViewResultsSelectionForeground = registerColor('peekViewResult. export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hcDark: Color.black, hcLight: Color.white }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.')); export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); export const peekViewEditorStickyScrollBackground = registerColor('peekViewEditorStickyScroll.background', peekViewEditorBackground, nls.localize('peekViewEditorStickScrollBackground', 'Background color of sticky scroll in the peek view editor.')); +export const peekViewEditorStickyScrollGutterBackground = registerColor('peekViewEditorStickyScrollGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorStickyScrollGutterBackground', 'Background color of the gutter part of sticky scroll in the peek view editor.')); export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hcDark: null, hcLight: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.')); export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hcDark: null, hcLight: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.')); diff --git a/src/vs/editor/contrib/placeholderText/browser/placeholderText.css b/src/vs/editor/contrib/placeholderText/browser/placeholderText.css index 043b6f15632..0e533bad4c6 100644 --- a/src/vs/editor/contrib/placeholderText/browser/placeholderText.css +++ b/src/vs/editor/contrib/placeholderText/browser/placeholderText.css @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor { - --vscode-editor-placeholder-foreground: var(--vscode-editorGhostText-foreground); - .editorPlaceholder { top: 0px; position: absolute; diff --git a/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts index cd7cbd3e7c1..137abcc4826 100644 --- a/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts @@ -48,7 +48,7 @@ export async function getDocumentSemanticTokens(registry: LanguageFeatureRegistr // Get tokens from all providers at the same time. const results = await Promise.all(providers.map(async (provider) => { let result: SemanticTokens | SemanticTokensEdits | null | undefined; - let error: any = null; + let error: unknown = null; try { result = await provider.provideDocumentSemanticTokens(model, (provider === lastProvider ? lastResultId : null), token); } catch (err) { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index 12480664119..ecc59245dec 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -5,27 +5,42 @@ .monaco-editor .sticky-widget { overflow: hidden; + border-bottom: 1px solid var(--vscode-editorStickyScroll-border); + width: 100%; + box-shadow: var(--vscode-editorStickyScroll-shadow) 0 4px 2px -2px; + z-index: 4; + right: initial !important; + margin-left: '0px'; } -.monaco-editor .sticky-widget-line-numbers { +.monaco-editor .sticky-widget .sticky-widget-line-numbers { float: left; - background-color: inherit; + background-color: var(--vscode-editorStickyScrollGutter-background); } -.monaco-editor .sticky-widget-lines-scrollable { +.monaco-editor .sticky-widget.peek .sticky-widget-line-numbers { + background-color: var(--vscode-peekViewEditorStickyScrollGutter-background); +} + +.monaco-editor .sticky-widget .sticky-widget-lines-scrollable { display: inline-block; position: absolute; overflow: hidden; width: var(--vscode-editorStickyScroll-scrollableWidth); - background-color: inherit; + background-color: var(--vscode-editorStickyScroll-background); } -.monaco-editor .sticky-widget-lines { +.monaco-editor .sticky-widget.peek .sticky-widget-lines-scrollable { + background-color: var(--vscode-peekViewEditorStickyScroll-background); +} + +.monaco-editor .sticky-widget .sticky-widget-lines { position: absolute; background-color: inherit; } -.monaco-editor .sticky-line-number, .monaco-editor .sticky-line-content { +.monaco-editor .sticky-widget .sticky-line-number, +.monaco-editor .sticky-widget .sticky-line-content { color: var(--vscode-editorLineNumber-foreground); white-space: nowrap; display: inline-block; @@ -33,42 +48,26 @@ background-color: inherit; } -.monaco-editor .sticky-line-number .codicon-folding-expanded, -.monaco-editor .sticky-line-number .codicon-folding-collapsed { +.monaco-editor .sticky-widget .sticky-line-number .codicon-folding-expanded, +.monaco-editor .sticky-widget .sticky-line-number .codicon-folding-collapsed { float: right; transition: var(--vscode-editorStickyScroll-foldingOpacityTransition); position: absolute; margin-left: 2px; } -.monaco-editor .sticky-line-content { +.monaco-editor .sticky-widget .sticky-line-content { width: var(--vscode-editorStickyScroll-scrollableWidth); background-color: inherit; white-space: nowrap; } -.monaco-editor .sticky-line-number-inner { +.monaco-editor .sticky-widget .sticky-line-number-inner { display: inline-block; text-align: right; } -.monaco-editor .sticky-widget { - border-bottom: 1px solid var(--vscode-editorStickyScroll-border); -} - -.monaco-editor .sticky-line-content:hover { +.monaco-editor .sticky-widget .sticky-line-content:hover { background-color: var(--vscode-editorStickyScrollHover-background); cursor: pointer; } - -.monaco-editor .sticky-widget { - width: 100%; - box-shadow: var(--vscode-editorStickyScroll-shadow) 0 4px 2px -2px; - z-index: 4; - background-color: var(--vscode-editorStickyScroll-background); - right: initial !important; -} - -.monaco-editor .sticky-widget.peek { - background-color: var(--vscode-peekViewEditorStickyScroll-background); -} diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 9f443e037d6..736492e7292 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -310,26 +310,18 @@ export class StickyScrollController extends Disposable implements IEditorContrib } this._revealPosition(position); })); - const mouseMoveListener = (mouseEvent: MouseEvent) => { + this._register(dom.addDisposableListener(mainWindow, dom.EventType.MOUSE_MOVE, mouseEvent => { this._mouseTarget = mouseEvent.target; this._onMouseMoveOrKeyDown(mouseEvent); - }; - const keyDownListener = (mouseEvent: KeyboardEvent) => { + })); + this._register(dom.addDisposableListener(mainWindow, dom.EventType.KEY_DOWN, mouseEvent => { this._onMouseMoveOrKeyDown(mouseEvent); - }; - const keyUpListener = (e: KeyboardEvent) => { + })); + this._register(dom.addDisposableListener(mainWindow, dom.EventType.KEY_UP, () => { if (this._showEndForLine !== undefined) { this._showEndForLine = undefined; this._renderStickyScroll(); } - }; - mainWindow.addEventListener(dom.EventType.MOUSE_MOVE, mouseMoveListener); - mainWindow.addEventListener(dom.EventType.KEY_DOWN, keyDownListener); - mainWindow.addEventListener(dom.EventType.KEY_UP, keyUpListener); - this._register(toDisposable(() => { - mainWindow.removeEventListener(dom.EventType.MOUSE_MOVE, mouseMoveListener); - mainWindow.removeEventListener(dom.EventType.KEY_DOWN, keyDownListener); - mainWindow.removeEventListener(dom.EventType.KEY_UP, keyUpListener); })); this._register(gesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, _keyboardEvent]) => { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts index 7b0800a58c4..14111d8b810 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { IActiveCodeEditor } from '../../../browser/editorBrowser.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { OutlineElement, OutlineGroup, OutlineModel } from '../../documentSymbols/browser/outlineModel.js'; @@ -385,17 +385,26 @@ class StickyModelFromCandidateIndentationFoldingProvider extends StickyModelFrom class StickyModelFromCandidateSyntaxFoldingProvider extends StickyModelFromCandidateFoldingProvider { - private readonly provider: SyntaxRangeProvider | undefined; + private readonly provider: MutableDisposable = this._register(new MutableDisposable()); - constructor(editor: IActiveCodeEditor, + constructor( + editor: IActiveCodeEditor, onProviderUpdate: () => void, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService ) { super(editor); + this._register(this._languageFeaturesService.foldingRangeProvider.onDidChange(() => { + this._updateProvider(editor, onProviderUpdate); + })); + this._updateProvider(editor, onProviderUpdate); + } + + private _updateProvider(editor: IActiveCodeEditor, onProviderUpdate: () => void): void { const selectedProviders = FoldingController.getFoldingRangeProviders(this._languageFeaturesService, editor.getModel()); - if (selectedProviders.length > 0) { - this.provider = this._register(new SyntaxRangeProvider(editor.getModel(), selectedProviders, onProviderUpdate, this._foldingLimitReporter, undefined)); + if (selectedProviders.length === 0) { + return; } + this.provider.value = new SyntaxRangeProvider(editor.getModel(), selectedProviders, onProviderUpdate, this._foldingLimitReporter, undefined); } protected override isProviderValid(): boolean { @@ -403,6 +412,6 @@ class StickyModelFromCandidateSyntaxFoldingProvider extends StickyModelFromCandi } protected override async createModelFromProvider(token: CancellationToken): Promise { - return this.provider?.compute(token) ?? null; + return this.provider.value?.compute(token) ?? null; } } diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts index d5d585d073b..ec587d8c717 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts @@ -9,7 +9,6 @@ import { ILanguageFeaturesService } from '../../../common/services/languageFeatu import { CancellationToken, CancellationTokenSource, } from '../../../../base/common/cancellation.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; -import { Range } from '../../../common/core/range.js'; import { binarySearch } from '../../../../base/common/arrays.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js'; @@ -27,17 +26,33 @@ export class StickyLineCandidate { } export interface IStickyLineCandidateProvider { - + /** + * Dispose resources used by the provider. + */ dispose(): void; - getVersionId(): number | undefined; - update(): Promise; - getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[]; - onDidChangeStickyScroll: Event; + /** + * Get the version ID of the sticky model. + */ + getVersionId(): number | undefined; + + /** + * Update the sticky line candidates. + */ + update(): Promise; + + /** + * Get sticky line candidates intersecting a given range. + */ + getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[]; + + /** + * Event triggered when sticky scroll changes. + */ + onDidChangeStickyScroll: Event; } export class StickyLineCandidateProvider extends Disposable implements IStickyLineCandidateProvider { - static readonly ID = 'store.contrib.stickyScrollController'; private readonly _onDidChangeStickyScroll = this._register(new Emitter()); @@ -69,6 +84,9 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi this.readConfiguration(); } + /** + * Read and apply the sticky scroll configuration. + */ private readConfiguration() { this._sessionStore.clear(); const options = this._editor.getOption(EditorOption.stickyScroll); @@ -76,12 +94,9 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi return; } this._sessionStore.add(this._editor.onDidChangeModel(() => { - // We should not show an old model for a different file, it will always be wrong. - // So we clear the model here immediately and then trigger an update. this._model = null; this.updateStickyModelProvider(); this._onDidChangeStickyScroll.fire(); - this.update(); })); this._sessionStore.add(this._editor.onDidChangeHiddenAreas(() => this.update())); @@ -95,17 +110,22 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi this.update(); } + /** + * Get the version ID of the sticky model. + */ public getVersionId(): number | undefined { return this._model?.version; } + /** + * Update the sticky model provider. + */ private updateStickyModelProvider() { this._stickyModelProvider?.dispose(); this._stickyModelProvider = null; - const editor = this._editor; - if (editor.hasModel()) { + if (this._editor.hasModel()) { this._stickyModelProvider = new StickyModelProvider( - editor, + this._editor, () => this._updateSoon.schedule(), this._languageConfigurationService, this._languageFeaturesService @@ -113,6 +133,9 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi } } + /** + * Update the sticky line candidates. + */ public async update(): Promise { this._cts?.dispose(true); this._cts = new CancellationTokenSource(); @@ -120,29 +143,36 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi this._onDidChangeStickyScroll.fire(); } + /** + * Update the sticky model based on the current editor state. + */ private async updateStickyModel(token: CancellationToken): Promise { if (!this._editor.hasModel() || !this._stickyModelProvider || this._editor.getModel().isTooLargeForTokenization()) { this._model = null; return; } const model = await this._stickyModelProvider.update(token); - if (token.isCancellationRequested) { - // the computation was canceled, so do not overwrite the model - return; + if (!token.isCancellationRequested) { + this._model = model; } - this._model = model; } - private updateIndex(index: number) { - if (index === -1) { - index = 0; - } else if (index < 0) { - index = -index - 2; + /** + * Get sticky line candidates intersecting a given range. + */ + public getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[] { + if (!this._model?.element) { + return []; } - return index; + const stickyLineCandidates: StickyLineCandidate[] = []; + this.getCandidateStickyLinesIntersectingFromStickyModel(range, this._model.element, stickyLineCandidates, 0, 0, -1); + return this.filterHiddenRanges(stickyLineCandidates); } - public getCandidateStickyLinesIntersectingFromStickyModel( + /** + * Get sticky line candidates intersecting a given range from the sticky model. + */ + private getCandidateStickyLinesIntersectingFromStickyModel( range: StickyRange, outlineModel: StickyElement, result: StickyLineCandidate[], @@ -167,38 +197,44 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi for (let i = lowerBound; i <= upperBound; i++) { const child = outlineModel.children[i]; - if (!child) { - return; + if (!child || !child.range) { + continue; } - const childRange = child.range; - if (childRange) { - const childStartLine = childRange.startLineNumber; - const childEndLine = childRange.endLineNumber; - if (range.startLineNumber <= childEndLine + 1 && childStartLine - 1 <= range.endLineNumber && childStartLine !== lastLine) { - lastLine = childStartLine; - const lineHeight = this._editor.getLineHeightForPosition(new Position(childStartLine, 1)); - result.push(new StickyLineCandidate(childStartLine, childEndLine - 1, top, lineHeight)); - this.getCandidateStickyLinesIntersectingFromStickyModel(range, child, result, depth + 1, top + lineHeight, childStartLine); - } - } else { - this.getCandidateStickyLinesIntersectingFromStickyModel(range, child, result, depth, top, lastStartLineNumber); + const { startLineNumber, endLineNumber } = child.range; + if (range.startLineNumber <= endLineNumber + 1 && startLineNumber - 1 <= range.endLineNumber && startLineNumber !== lastLine) { + lastLine = startLineNumber; + const lineHeight = this._editor.getLineHeightForPosition(new Position(startLineNumber, 1)); + result.push(new StickyLineCandidate(startLineNumber, endLineNumber - 1, top, lineHeight)); + this.getCandidateStickyLinesIntersectingFromStickyModel(range, child, result, depth + 1, top + lineHeight, startLineNumber); } } } - public getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[] { - if (!this._model?.element) { - return []; + /** + * Filter out sticky line candidates that are within hidden ranges. + */ + private filterHiddenRanges(stickyLineCandidates: StickyLineCandidate[]): StickyLineCandidate[] { + const hiddenRanges = this._editor._getViewModel()?.getHiddenAreas(); + if (!hiddenRanges) { + return stickyLineCandidates; } - let stickyLineCandidates: StickyLineCandidate[] = []; - this.getCandidateStickyLinesIntersectingFromStickyModel(range, this._model.element, stickyLineCandidates, 0, 0, -1); - const hiddenRanges: Range[] | undefined = this._editor._getViewModel()?.getHiddenAreas(); + return stickyLineCandidates.filter(candidate => { + return !hiddenRanges.some(hiddenRange => + candidate.startLineNumber >= hiddenRange.startLineNumber && + candidate.endLineNumber <= hiddenRange.endLineNumber + 1 + ); + }); + } - if (hiddenRanges) { - for (const hiddenRange of hiddenRanges) { - stickyLineCandidates = stickyLineCandidates.filter(stickyLine => !(stickyLine.startLineNumber >= hiddenRange.startLineNumber && stickyLine.endLineNumber <= hiddenRange.endLineNumber + 1)); - } + /** + * Update the binary search index. + */ + private updateIndex(index: number): number { + if (index === -1) { + return 0; + } else if (index < 0) { + return -index - 2; } - return stickyLineCandidates; + return index; } } diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 1abf94328e2..a642c3c0c77 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -20,6 +20,7 @@ import { CharacterMapping, RenderLineInput, renderViewLine } from '../../../comm import { foldingCollapsedIcon, foldingExpandedIcon } from '../../folding/browser/foldingDecorations.js'; import { FoldingModel } from '../../folding/browser/foldingModel.js'; import { Emitter } from '../../../../base/common/event.js'; +import { IViewModel } from '../../../common/viewModel.js'; export class StickyScrollWidgetState { constructor( @@ -58,7 +59,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { private readonly _editor: ICodeEditor; - private _previousState: StickyScrollWidgetState | undefined; + private _state: StickyScrollWidgetState | undefined; private _lineHeight: number; private _renderedStickyLines: RenderedStickyLine[] = []; private _lineNumbers: number[] = []; @@ -142,49 +143,48 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { return this._lineNumbers; } - setState(_state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | undefined, _rebuildFromLine?: number): void { - if (_rebuildFromLine === undefined && - ((!this._previousState && !_state) || (this._previousState && this._previousState.equals(_state))) - ) { + setState(state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | undefined, rebuildFromIndexCandidate?: number): void { + const currentStateAndPreviousStateUndefined = !this._state && !state; + const currentStateDefinedAndEqualsPreviousState = this._state && this._state.equals(state); + if (currentStateAndPreviousStateUndefined || currentStateDefinedAndEqualsPreviousState) { return; } - const isWidgetHeightZero = this._isWidgetHeightZero(_state); - const state = isWidgetHeightZero ? undefined : _state; - const rebuildFromLine = isWidgetHeightZero ? 0 : this._findLineToRebuildWidgetFrom(_state, _rebuildFromLine); - - this._renderRootNode(state, foldingModel, rebuildFromLine); - this._previousState = _state; + const data = this._findRenderingData(state); + const previousLineNumbers = this._lineNumbers; + this._lineNumbers = data.lineNumbers; + this._lastLineRelativePosition = data.lastLineRelativePosition; + const rebuildFromIndex = this._findIndexToRebuildFrom(previousLineNumbers, this._lineNumbers, rebuildFromIndexCandidate); + this._renderRootNode(this._lineNumbers, this._lastLineRelativePosition, foldingModel, rebuildFromIndex); + this._state = state; } - private _isWidgetHeightZero(state: StickyScrollWidgetState | undefined): boolean { + private _findRenderingData(state: StickyScrollWidgetState | undefined): { lineNumbers: number[]; lastLineRelativePosition: number } { if (!state) { - return true; + return { lineNumbers: [], lastLineRelativePosition: 0 }; } - const futureWidgetHeight = this._getHeightOfLines(state.startLineNumbers, state.lastLineRelativePosition); - if (futureWidgetHeight > 0) { - this._lastLineRelativePosition = state.lastLineRelativePosition; - const lineNumbers = [...state.startLineNumbers]; - if (state.showEndForLine !== null) { - lineNumbers[state.showEndForLine] = state.endLineNumbers[state.showEndForLine]; - } - this._lineNumbers = lineNumbers; - } else { - this._lastLineRelativePosition = 0; - this._lineNumbers = []; + const candidateLineNumbers = [...state.startLineNumbers]; + if (state.showEndForLine !== null) { + candidateLineNumbers[state.showEndForLine] = state.endLineNumbers[state.showEndForLine]; } - return futureWidgetHeight === 0; + let totalHeight = 0; + for (let i = 0; i < candidateLineNumbers.length; i++) { + totalHeight += this._editor.getLineHeightForPosition(new Position(candidateLineNumbers[i], 1)); + } + if (totalHeight === 0) { + return { lineNumbers: [], lastLineRelativePosition: 0 }; + } + return { lineNumbers: candidateLineNumbers, lastLineRelativePosition: state.lastLineRelativePosition }; } - private _findLineToRebuildWidgetFrom(state: StickyScrollWidgetState | undefined, _rebuildFromLine?: number): number { - if (!state || !this._previousState) { + private _findIndexToRebuildFrom(previousLineNumbers: number[], newLineNumbers: number[], rebuildFromIndexCandidate?: number): number { + if (newLineNumbers.length === 0) { return 0; } - if (_rebuildFromLine !== undefined) { - return _rebuildFromLine; + if (rebuildFromIndexCandidate !== undefined) { + return rebuildFromIndexCandidate; } - const previousState = this._previousState; - const indexOfLinesAlreadyRendered = state.startLineNumbers.findIndex(startLineNumber => !previousState.startLineNumbers.includes(startLineNumber)); - return (indexOfLinesAlreadyRendered === -1) ? 0 : indexOfLinesAlreadyRendered; + const validIndex = newLineNumbers.findIndex(startLineNumber => !previousLineNumbers.includes(startLineNumber)); + return validIndex === -1 ? 0 : validIndex; } private _updateWidgetWidth(): void { @@ -195,18 +195,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._rootDomNode.style.width = `${layoutInfo.width - layoutInfo.verticalScrollbarWidth}px`; } - private _clearStickyLinesFromLine(clearFromLine: number) { - this._foldingIconStore.clear(); - // Removing only the lines that need to be rerendered - for (let i = clearFromLine; i < this._renderedStickyLines.length; i++) { - const stickyLine = this._renderedStickyLines[i]; - stickyLine.lineNumberDomNode.remove(); - stickyLine.lineDomNode.remove(); - } - // Keep the lines that need to be updated - this._renderedStickyLines = this._renderedStickyLines.slice(0, clearFromLine); - } - private _useFoldingOpacityTransition(requireTransitions: boolean) { this._lineNumbersDomNode.style.setProperty('--vscode-editorStickyScroll-foldingOpacityTransition', `opacity ${requireTransitions ? 0.5 : 0}s`); } @@ -221,51 +209,55 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } } - private async _renderRootNode(state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | undefined, rebuildFromLine: number): Promise { - this._clearStickyLinesFromLine(rebuildFromLine); - if (!state) { - // make sure the dom is 0 height and display:none - this._setHeight(0); + private async _renderRootNode(lineNumbers: number[], lastLineRelativePosition: number, foldingModel: FoldingModel | undefined, rebuildFromIndex: number): Promise { + const viewModel = this._editor._getViewModel(); + if (!viewModel) { + this._clearWidget(); return; } - let top: number = 0; - // For existing sticky lines update the top and z-index - for (const stickyLine of this._renderedStickyLines) { - this._updatePosition(stickyLine, top); - top += stickyLine.height; + if (lineNumbers.length === 0) { + this._clearWidget(); + return; } - // For new sticky lines - const layoutInfo = this._editor.getLayoutInfo(); - const linesToRender = this._lineNumbers.slice(rebuildFromLine); - for (const [index, line] of linesToRender.entries()) { - const stickyLine = this._renderChildNode(index + rebuildFromLine, line, top, foldingModel, layoutInfo); - if (!stickyLine) { - continue; + const renderedStickyLines: RenderedStickyLine[] = []; + const lastLineNumber = lineNumbers[lineNumbers.length - 1]; + let top: number = 0; + for (let i = 0; i < this._renderedStickyLines.length; i++) { + if (i < rebuildFromIndex) { + const renderedLine = this._renderedStickyLines[i]; + renderedStickyLines.push(this._updatePosition(renderedLine, top, renderedLine.lineNumber === lastLineNumber)); + top += renderedLine.height; + } else { + const renderedLine = this._renderedStickyLines[i]; + renderedLine.lineNumberDomNode.remove(); + renderedLine.lineDomNode.remove(); } + } + const layoutInfo = this._editor.getLayoutInfo(); + for (let i = rebuildFromIndex; i < lineNumbers.length; i++) { + const stickyLine = this._renderChildNode(viewModel, i, lineNumbers[i], top, lastLineNumber === lineNumbers[i], foldingModel, layoutInfo); top += stickyLine.height; this._linesDomNode.appendChild(stickyLine.lineDomNode); this._lineNumbersDomNode.appendChild(stickyLine.lineNumberDomNode); - this._renderedStickyLines.push(stickyLine); + renderedStickyLines.push(stickyLine); } if (foldingModel) { this._setFoldingHoverListeners(); this._useFoldingOpacityTransition(!this._isOnGlyphMargin); } - - const widgetHeight = top + this._lastLineRelativePosition; - this._setHeight(widgetHeight); - - this._rootDomNode.style.marginLeft = '0px'; this._minContentWidthInPx = Math.max(...this._renderedStickyLines.map(l => l.scrollWidth)) + layoutInfo.verticalScrollbarWidth; + this._renderedStickyLines = renderedStickyLines; + this._setHeight(top + lastLineRelativePosition); this._editor.layoutOverlayWidget(this); } - private _getHeightOfLines(lineNumbers: number[], lastLineRelativePosition: number): number { - let totalHeight = 0; - for (let i = 0; i < lineNumbers.length; i++) { - totalHeight += this._editor.getLineHeightForPosition(new Position(lineNumbers[i], 1)); + private _clearWidget(): void { + for (let i = 0; i < this._renderedStickyLines.length; i++) { + const stickyLine = this._renderedStickyLines[i]; + stickyLine.lineNumberDomNode.remove(); + stickyLine.lineDomNode.remove(); } - return totalHeight + lastLineRelativePosition; + this._setHeight(0); } private _setHeight(height: number): void { @@ -302,11 +294,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { })); } - private _renderChildNode(index: number, line: number, top: number, foldingModel: FoldingModel | undefined, layoutInfo: EditorLayoutInfo): RenderedStickyLine | undefined { - const viewModel = this._editor._getViewModel(); - if (!viewModel) { - return; - } + private _renderChildNode(viewModel: IViewModel, index: number, line: number, top: number, isLastLine: boolean, foldingModel: FoldingModel | undefined, layoutInfo: EditorLayoutInfo): RenderedStickyLine { const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(line, 1)).lineNumber; const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber); const lineNumberOption = this._editor.getOption(EditorOption.lineNumbers); @@ -376,21 +364,27 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._editor.applyFontInfo(lineHTMLNode); this._editor.applyFontInfo(lineNumberHTMLNode); - lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`; lineHTMLNode.style.lineHeight = `${lineHeight}px`; lineNumberHTMLNode.style.height = `${lineHeight}px`; lineHTMLNode.style.height = `${lineHeight}px`; - const renderedLine = new RenderedStickyLine(index, line, lineHTMLNode, lineNumberHTMLNode, foldingIcon, renderOutput.characterMapping, lineHTMLNode.scrollWidth, lineHeight); - return this._updatePosition(renderedLine, top); + const renderedLine = new RenderedStickyLine( + index, + line, + lineHTMLNode, + lineNumberHTMLNode, + foldingIcon, + renderOutput.characterMapping, + lineHTMLNode.scrollWidth, + lineHeight + ); + return this._updatePosition(renderedLine, top, isLastLine); } - private _updatePosition(stickyLine: RenderedStickyLine, top: number): RenderedStickyLine { - const index = stickyLine.index; + private _updatePosition(stickyLine: RenderedStickyLine, top: number, isLastLine: boolean): RenderedStickyLine { const lineHTMLNode = stickyLine.lineDomNode; const lineNumberHTMLNode = stickyLine.lineNumberDomNode; - const isLastLine = index === this._lineNumbers.length - 1; if (isLastLine) { const zIndex = '0'; lineHTMLNode.style.zIndex = zIndex; diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetRenderer.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetRenderer.ts index 3d0850da13b..dbb998c3947 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetRenderer.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetRenderer.ts @@ -134,6 +134,7 @@ export class ItemRenderer implements IListRenderer(client: TCli if (typeof prop !== 'string') { throw new Error(`Not supported`); } - return (...args: any[]) => { + return (...args: unknown[]) => { return editorWorkerHost.$fhr(prop, args); }; } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f61c5f40b51..8018e740261 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -591,8 +591,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { return ''; } - public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { - // noop + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): IDisposable { + return Disposable.None; } /** diff --git a/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts b/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts index 2b0e55aab2c..41682b417f1 100644 --- a/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts +++ b/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts @@ -12,6 +12,7 @@ import { TextureAtlas } from '../../../../browser/gpu/atlas/textureAtlas.js'; import { createCodeEditorServices } from '../../testCodeEditor.js'; import { assertIsValidGlyph } from './testUtil.js'; import { TextureAtlasSlabAllocator } from '../../../../browser/gpu/atlas/textureAtlasSlabAllocator.js'; +import { DecorationStyleCache } from '../../../../browser/gpu/css/decorationStyleCache.js'; const blackInt = 0x000000FF; const nullCharMetadata = 0x0; @@ -79,7 +80,7 @@ suite('TextureAtlas', () => { setup(() => { instantiationService = createCodeEditorServices(store); - atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, undefined)); + atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, undefined, new DecorationStyleCache())); glyphRasterizer = new TestGlyphRasterizer(); glyphRasterizer.nextGlyphDimensions = [1, 1]; glyphRasterizer.nextGlyphColor = [0, 0, 0, 0xFF]; @@ -90,7 +91,7 @@ suite('TextureAtlas', () => { }); test('get multiple glyphs', () => { - atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, undefined)); + atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, undefined, new DecorationStyleCache())); for (let i = 0; i < 10; i++) { assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas); } @@ -119,14 +120,14 @@ suite('TextureAtlas', () => { glyphRasterizer.nextGlyphDimensions = [2, 2]; atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, { allocatorType: (canvas, textureIndex) => new TextureAtlasSlabAllocator(canvas, textureIndex, { slabW: 1, slabH: 1 }) - })); + }, new DecorationStyleCache())); assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas); }); test('adding a non-first glyph larger than the standard slab size, causing an overflow to a new page', () => { atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, { allocatorType: (canvas, textureIndex) => new TextureAtlasSlabAllocator(canvas, textureIndex, { slabW: 1, slabH: 1 }) - })); + }, new DecorationStyleCache())); assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas); strictEqual(atlas.pages.length, 1); glyphRasterizer.nextGlyphDimensions = [2, 2]; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 115c7b8640d..8960c3fc9b4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3355,6 +3355,10 @@ declare namespace monaco.editor { * Defaults to true. */ scrollBeyondLastLine?: boolean; + /** + * Scroll editor on middle click + */ + scrollOnMiddleClick?: boolean; /** * Enable that scrolling can go beyond the last column by a number of columns. * Defaults to 5. @@ -3568,6 +3572,14 @@ declare namespace monaco.editor { * Defaults to advanced. */ autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; + /** + * Boolean which controls whether to autoindent on paste + */ + autoIndentOnPaste?: boolean; + /** + * Boolean which controls whether to autoindent on paste within a string when autoIndentOnPaste is enabled. + */ + autoIndentOnPasteWithinString?: boolean; /** * Emulate selection behaviour of tab characters when using spaces for indentation. * This means selection will stick to tab stops. @@ -3825,7 +3837,7 @@ declare namespace monaco.editor { /** * Sets whether the new experimental edit context should be used instead of the text area. */ - experimentalEditContextEnabled?: boolean; + editContext?: boolean; /** * Controls support for changing how content is pasted into the editor. */ @@ -4925,150 +4937,153 @@ declare namespace monaco.editor { autoClosingOvertype = 11, autoClosingQuotes = 12, autoIndent = 13, - automaticLayout = 14, - autoSurround = 15, - bracketPairColorization = 16, - guides = 17, - codeLens = 18, - codeLensFontFamily = 19, - codeLensFontSize = 20, - colorDecorators = 21, - colorDecoratorsLimit = 22, - columnSelection = 23, - comments = 24, - contextmenu = 25, - copyWithSyntaxHighlighting = 26, - cursorBlinking = 27, - cursorSmoothCaretAnimation = 28, - cursorStyle = 29, - cursorSurroundingLines = 30, - cursorSurroundingLinesStyle = 31, - cursorWidth = 32, - disableLayerHinting = 33, - disableMonospaceOptimizations = 34, - domReadOnly = 35, - dragAndDrop = 36, - dropIntoEditor = 37, - experimentalEditContextEnabled = 38, - emptySelectionClipboard = 39, - experimentalGpuAcceleration = 40, - experimentalWhitespaceRendering = 41, - extraEditorClassName = 42, - fastScrollSensitivity = 43, - find = 44, - fixedOverflowWidgets = 45, - folding = 46, - foldingStrategy = 47, - foldingHighlight = 48, - foldingImportsByDefault = 49, - foldingMaximumRegions = 50, - unfoldOnClickAfterEndOfLine = 51, - fontFamily = 52, - fontInfo = 53, - fontLigatures = 54, - fontSize = 55, - fontWeight = 56, - fontVariations = 57, - formatOnPaste = 58, - formatOnType = 59, - glyphMargin = 60, - gotoLocation = 61, - hideCursorInOverviewRuler = 62, - hover = 63, - inDiffEditor = 64, - inlineSuggest = 65, - letterSpacing = 66, - lightbulb = 67, - lineDecorationsWidth = 68, - lineHeight = 69, - lineNumbers = 70, - lineNumbersMinChars = 71, - linkedEditing = 72, - links = 73, - matchBrackets = 74, - minimap = 75, - mouseStyle = 76, - mouseWheelScrollSensitivity = 77, - mouseWheelZoom = 78, - multiCursorMergeOverlapping = 79, - multiCursorModifier = 80, - multiCursorPaste = 81, - multiCursorLimit = 82, - occurrencesHighlight = 83, - occurrencesHighlightDelay = 84, - overtypeCursorStyle = 85, - overtypeOnPaste = 86, - overviewRulerBorder = 87, - overviewRulerLanes = 88, - padding = 89, - pasteAs = 90, - parameterHints = 91, - peekWidgetDefaultFocus = 92, - placeholder = 93, - definitionLinkOpensInPeek = 94, - quickSuggestions = 95, - quickSuggestionsDelay = 96, - readOnly = 97, - readOnlyMessage = 98, - renameOnType = 99, - renderControlCharacters = 100, - renderFinalNewline = 101, - renderLineHighlight = 102, - renderLineHighlightOnlyWhenFocus = 103, - renderValidationDecorations = 104, - renderWhitespace = 105, - revealHorizontalRightPadding = 106, - roundedSelection = 107, - rulers = 108, - scrollbar = 109, - scrollBeyondLastColumn = 110, - scrollBeyondLastLine = 111, - scrollPredominantAxis = 112, - selectionClipboard = 113, - selectionHighlight = 114, - selectOnLineNumbers = 115, - showFoldingControls = 116, - showUnused = 117, - snippetSuggestions = 118, - smartSelect = 119, - smoothScrolling = 120, - stickyScroll = 121, - stickyTabStops = 122, - stopRenderingLineAfter = 123, - suggest = 124, - suggestFontSize = 125, - suggestLineHeight = 126, - suggestOnTriggerCharacters = 127, - suggestSelection = 128, - tabCompletion = 129, - tabIndex = 130, - unicodeHighlighting = 131, - unusualLineTerminators = 132, - useShadowDOM = 133, - useTabStops = 134, - wordBreak = 135, - wordSegmenterLocales = 136, - wordSeparators = 137, - wordWrap = 138, - wordWrapBreakAfterCharacters = 139, - wordWrapBreakBeforeCharacters = 140, - wordWrapColumn = 141, - wordWrapOverride1 = 142, - wordWrapOverride2 = 143, - wrappingIndent = 144, - wrappingStrategy = 145, - showDeprecated = 146, - inlayHints = 147, - effectiveCursorStyle = 148, - editorClassName = 149, - pixelRatio = 150, - tabFocusMode = 151, - layoutInfo = 152, - wrappingInfo = 153, - defaultColorDecorators = 154, - colorDecoratorsActivatedOn = 155, - inlineCompletionsAccessibilityVerbose = 156, - effectiveExperimentalEditContextEnabled = 157 + autoIndentOnPaste = 14, + autoIndentOnPasteWithinString = 15, + automaticLayout = 16, + autoSurround = 17, + bracketPairColorization = 18, + guides = 19, + codeLens = 20, + codeLensFontFamily = 21, + codeLensFontSize = 22, + colorDecorators = 23, + colorDecoratorsLimit = 24, + columnSelection = 25, + comments = 26, + contextmenu = 27, + copyWithSyntaxHighlighting = 28, + cursorBlinking = 29, + cursorSmoothCaretAnimation = 30, + cursorStyle = 31, + cursorSurroundingLines = 32, + cursorSurroundingLinesStyle = 33, + cursorWidth = 34, + disableLayerHinting = 35, + disableMonospaceOptimizations = 36, + domReadOnly = 37, + dragAndDrop = 38, + dropIntoEditor = 39, + editContext = 40, + emptySelectionClipboard = 41, + experimentalGpuAcceleration = 42, + experimentalWhitespaceRendering = 43, + extraEditorClassName = 44, + fastScrollSensitivity = 45, + find = 46, + fixedOverflowWidgets = 47, + folding = 48, + foldingStrategy = 49, + foldingHighlight = 50, + foldingImportsByDefault = 51, + foldingMaximumRegions = 52, + unfoldOnClickAfterEndOfLine = 53, + fontFamily = 54, + fontInfo = 55, + fontLigatures = 56, + fontSize = 57, + fontWeight = 58, + fontVariations = 59, + formatOnPaste = 60, + formatOnType = 61, + glyphMargin = 62, + gotoLocation = 63, + hideCursorInOverviewRuler = 64, + hover = 65, + inDiffEditor = 66, + inlineSuggest = 67, + letterSpacing = 68, + lightbulb = 69, + lineDecorationsWidth = 70, + lineHeight = 71, + lineNumbers = 72, + lineNumbersMinChars = 73, + linkedEditing = 74, + links = 75, + matchBrackets = 76, + minimap = 77, + mouseStyle = 78, + mouseWheelScrollSensitivity = 79, + mouseWheelZoom = 80, + multiCursorMergeOverlapping = 81, + multiCursorModifier = 82, + multiCursorPaste = 83, + multiCursorLimit = 84, + occurrencesHighlight = 85, + occurrencesHighlightDelay = 86, + overtypeCursorStyle = 87, + overtypeOnPaste = 88, + overviewRulerBorder = 89, + overviewRulerLanes = 90, + padding = 91, + pasteAs = 92, + parameterHints = 93, + peekWidgetDefaultFocus = 94, + placeholder = 95, + definitionLinkOpensInPeek = 96, + quickSuggestions = 97, + quickSuggestionsDelay = 98, + readOnly = 99, + readOnlyMessage = 100, + renameOnType = 101, + renderControlCharacters = 102, + renderFinalNewline = 103, + renderLineHighlight = 104, + renderLineHighlightOnlyWhenFocus = 105, + renderValidationDecorations = 106, + renderWhitespace = 107, + revealHorizontalRightPadding = 108, + roundedSelection = 109, + rulers = 110, + scrollbar = 111, + scrollBeyondLastColumn = 112, + scrollBeyondLastLine = 113, + scrollPredominantAxis = 114, + selectionClipboard = 115, + selectionHighlight = 116, + selectOnLineNumbers = 117, + showFoldingControls = 118, + showUnused = 119, + snippetSuggestions = 120, + smartSelect = 121, + smoothScrolling = 122, + stickyScroll = 123, + stickyTabStops = 124, + stopRenderingLineAfter = 125, + suggest = 126, + suggestFontSize = 127, + suggestLineHeight = 128, + suggestOnTriggerCharacters = 129, + suggestSelection = 130, + tabCompletion = 131, + tabIndex = 132, + unicodeHighlighting = 133, + unusualLineTerminators = 134, + useShadowDOM = 135, + useTabStops = 136, + wordBreak = 137, + wordSegmenterLocales = 138, + wordSeparators = 139, + wordWrap = 140, + wordWrapBreakAfterCharacters = 141, + wordWrapBreakBeforeCharacters = 142, + wordWrapColumn = 143, + wordWrapOverride1 = 144, + wordWrapOverride2 = 145, + wrappingIndent = 146, + wrappingStrategy = 147, + showDeprecated = 148, + inlayHints = 149, + effectiveCursorStyle = 150, + editorClassName = 151, + pixelRatio = 152, + tabFocusMode = 153, + layoutInfo = 154, + wrappingInfo = 155, + defaultColorDecorators = 156, + colorDecoratorsActivatedOn = 157, + inlineCompletionsAccessibilityVerbose = 158, + effectiveEditContext = 159, + scrollOnMiddleClick = 160 } export const EditorOptions: { @@ -5086,6 +5101,8 @@ declare namespace monaco.editor { autoClosingOvertype: IEditorOption; autoClosingQuotes: IEditorOption; autoIndent: IEditorOption; + autoIndentOnPaste: IEditorOption; + autoIndentOnPasteWithinString: IEditorOption; automaticLayout: IEditorOption; autoSurround: IEditorOption; bracketPairColorization: IEditorOption>>; @@ -5114,7 +5131,7 @@ declare namespace monaco.editor { dragAndDrop: IEditorOption; emptySelectionClipboard: IEditorOption; dropIntoEditor: IEditorOption>>; - experimentalEditContextEnabled: IEditorOption; + editContext: IEditorOption; stickyScroll: IEditorOption>>; experimentalGpuAcceleration: IEditorOption; experimentalWhitespaceRendering: IEditorOption; @@ -5186,6 +5203,7 @@ declare namespace monaco.editor { scrollbar: IEditorOption; scrollBeyondLastColumn: IEditorOption; scrollBeyondLastLine: IEditorOption; + scrollOnMiddleClick: IEditorOption; scrollPredominantAxis: IEditorOption; selectionClipboard: IEditorOption; selectionHighlight: IEditorOption; @@ -5229,7 +5247,7 @@ declare namespace monaco.editor { wrappingInfo: IEditorOption; wrappingIndent: IEditorOption; wrappingStrategy: IEditorOption; - effectiveExperimentalEditContextEnabled: IEditorOption; + effectiveEditContextEnabled: IEditorOption; }; type EditorOptionsType = typeof EditorOptions; @@ -6709,14 +6727,30 @@ declare namespace monaco.languages { }>; } + /** + * Configuration for line comments. + */ + export interface LineCommentConfig { + /** + * The line comment token, like `//` + */ + comment: string; + /** + * Whether the comment token should not be indented and placed at the first column. + * Defaults to false. + */ + noIndent?: boolean; + } + /** * Describes how comments for a language work. */ export interface CommentRule { /** - * The line comment token, like `// this is a comment` + * The line comment token, like `// this is a comment`. + * Can be a string or an object with comment and optional noIndent properties. */ - lineComment?: string | null; + lineComment?: string | LineCommentConfig | null; /** * The block comment character pair, like `/* block comment */` */ diff --git a/src/vs/nls.ts b/src/vs/nls.ts index e730d0a761e..e9183ad7d32 100644 --- a/src/vs/nls.ts +++ b/src/vs/nls.ts @@ -8,6 +8,7 @@ import { getNLSLanguage, getNLSMessages } from './nls.messages.js'; // eslint-disable-next-line local/code-import-patterns export { getNLSLanguage, getNLSMessages } from './nls.messages.js'; +declare const document: { location?: { hash?: string } } | undefined; const isPseudo = getNLSLanguage() === 'pseudo' || (typeof document !== 'undefined' && document.location && typeof document.location.hash === 'string' && document.location.hash.indexOf('pseudo=true') >= 0); export interface ILocalizeInfo { diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index e4382f461f8..8bc11939b10 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { addDisposableListener } from '../../../base/browser/dom.js'; import { CachedFunction } from '../../../base/common/cache.js'; import { getStructuralKey } from '../../../base/common/equals.js'; import { Event, IValueWithChangeEvent } from '../../../base/common/event.js'; -import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { FileAccess } from '../../../base/common/network.js'; import { derived, observableFromEvent, ValueWithChangeEventFromObservable } from '../../../base/common/observable.js'; import { localize } from '../../../nls.js'; @@ -277,17 +278,26 @@ function checkEnabledState(state: EnabledState, getScreenReaderAttached: () => b * Play the given audio url. * @volume value between 0 and 1 */ -function playAudio(url: string, volume: number): Promise { - return new Promise((resolve, reject) => { +async function playAudio(url: string, volume: number): Promise { + const disposables = new DisposableStore(); + try { + return await doPlayAudio(url, volume, disposables); + } finally { + disposables.dispose(); + } +} + +function doPlayAudio(url: string, volume: number, disposables: DisposableStore): Promise { + return new Promise((resolve, reject) => { const audio = new Audio(url); audio.volume = volume; - audio.addEventListener('ended', () => { + disposables.add(addDisposableListener(audio, 'ended', () => { resolve(audio); - }); - audio.addEventListener('error', (e) => { + })); + disposables.add(addDisposableListener(audio, 'error', (e) => { // When the error event fires, ended might not be called reject(e.error); - }); + })); audio.play().catch(e => { // When play fails, the error event is not fired. reject(e); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0c9f7069eb1..1a45b866b6c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -210,7 +210,6 @@ export class MenuId { static readonly SidebarTitle = new MenuId('SidebarTitle'); static readonly PanelTitle = new MenuId('PanelTitle'); static readonly AuxiliaryBarTitle = new MenuId('AuxiliaryBarTitle'); - static readonly AuxiliaryBarHeader = new MenuId('AuxiliaryBarHeader'); static readonly TerminalInstanceContext = new MenuId('TerminalInstanceContext'); static readonly TerminalEditorInstanceContext = new MenuId('TerminalEditorInstanceContext'); static readonly TerminalNewDropdownContext = new MenuId('TerminalNewDropdownContext'); diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index 12754b8b00a..882dfac434c 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -93,7 +93,7 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar createWindow(details: HandlerDetails): BrowserWindowConstructorOptions { const { state, overrides } = this.computeWindowStateAndOverrides(details); return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, state, overrides, { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload-aux.js').fsPath + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload-aux.js').fsPath }); } diff --git a/src/vs/platform/diagnostics/electron-sandbox/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-browser/diagnosticsService.ts similarity index 95% rename from src/vs/platform/diagnostics/electron-sandbox/diagnosticsService.ts rename to src/vs/platform/diagnostics/electron-browser/diagnosticsService.ts index f984d59c5bc..340daeb42af 100644 --- a/src/vs/platform/diagnostics/electron-sandbox/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/electron-browser/diagnosticsService.ts @@ -4,6 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IDiagnosticsService } from '../common/diagnostics.js'; -import { registerSharedProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../ipc/electron-browser/services.js'; registerSharedProcessRemoteService(IDiagnosticsService, 'diagnostics'); diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts index 6a9e1fc437a..72e9060db8c 100644 --- a/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts +++ b/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts @@ -13,7 +13,7 @@ import { ICodeWindow } from '../../window/electron-main/window.js'; import { getAllWindowsExcludingOffscreen, IWindowsMainService } from '../../windows/electron-main/windows.js'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from '../../workspace/common/workspace.js'; import { IWorkspacesManagementMainService } from '../../workspaces/electron-main/workspacesManagementMainService.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { ILogService } from '../../log/common/log.js'; import { UtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js'; @@ -106,7 +106,7 @@ export class DiagnosticsMainService implements IDiagnosticsMainService { private async codeWindowToInfo(window: ICodeWindow): Promise { const folderURIs = await this.getFolderURIs(window); - const win = assertIsDefined(window.win); + const win = assertReturnsDefined(window.win); return this.browserWindowToInfo(win, folderURIs, window.remoteAuthority); } diff --git a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts index e0b99f38093..2a6acbac584 100644 --- a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts +++ b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts @@ -70,11 +70,6 @@ flakySuite('Native Modules (all platforms)', () => { assert.ok(typeof nodePty.spawn === 'function', testErrorMessage('node-pty')); }); - test('open', async () => { - const { default: open } = await import('open'); - assert.ok(typeof open === 'function', testErrorMessage('open')); - }); - test('@vscode/spdlog', async () => { const spdlog = await import('@vscode/spdlog'); assert.ok(typeof spdlog.createRotatingLogger === 'function', testErrorMessage('@vscode/spdlog')); diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/electron-browser/extensionsProfileScannerService.ts similarity index 100% rename from src/vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService.ts rename to src/vs/platform/extensionManagement/electron-browser/extensionsProfileScannerService.ts diff --git a/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.ts b/src/vs/platform/externalTerminal/electron-browser/externalTerminalService.ts similarity index 97% rename from src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.ts rename to src/vs/platform/externalTerminal/electron-browser/externalTerminalService.ts index ef1fe8b73df..cdea035f7c8 100644 --- a/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/electron-browser/externalTerminalService.ts @@ -5,7 +5,7 @@ import { IExternalTerminalService as ICommonExternalTerminalService } from '../common/externalTerminal.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; -import { registerMainProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../ipc/electron-browser/services.js'; export const IExternalTerminalService = createDecorator('externalTerminal'); diff --git a/src/vs/platform/files/browser/webFileSystemAccess.ts b/src/vs/platform/files/browser/webFileSystemAccess.ts index 24a91eb795f..f5e90b78a00 100644 --- a/src/vs/platform/files/browser/webFileSystemAccess.ts +++ b/src/vs/platform/files/browser/webFileSystemAccess.ts @@ -36,7 +36,6 @@ export namespace WebFileSystemAccess { } } -// TODO@bpasero adopt official types of FileSystemObserver export namespace WebFileSystemObserver { export function supported(obj: any & Window): boolean { diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts index 26a54be46ff..5484ddc3a4d 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts @@ -13,7 +13,6 @@ import { basename, dirname, join } from '../../../../../base/common/path.js'; import { isLinux, isMacintosh } from '../../../../../base/common/platform.js'; import { joinPath } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; -import { realpath } from '../../../../../base/node/extpath.js'; import { Promises } from '../../../../../base/node/pfs.js'; import { FileChangeFilter, FileChangeType, IFileChange } from '../../../common/files.js'; import { ILogMessage, coalesceEvents, INonRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe, isFiltered, isWatchRequestWithCorrelation } from '../../../common/watcher.js'; @@ -67,7 +66,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { let result = this.request.path; try { - result = await realpath(this.request.path); + result = await Promises.realpath(this.request.path); if (this.request.path !== result) { this.trace(`correcting a path to watch that seems to be a symbolic link (original: ${this.request.path}, real: ${result})`); diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index f57af9c5b7a..dce2f4db700 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -18,7 +18,7 @@ import { TernarySearchTree } from '../../../../../base/common/ternarySearchTree. import { normalizeNFC } from '../../../../../base/common/normalization.js'; import { normalize, join } from '../../../../../base/common/path.js'; import { isLinux, isMacintosh, isWindows } from '../../../../../base/common/platform.js'; -import { realcase, realpath } from '../../../../../base/node/extpath.js'; +import { Promises, realcase } from '../../../../../base/node/pfs.js'; import { FileChangeType, IFileChange } from '../../../common/files.js'; import { coalesceEvents, IRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe, isFiltered, IWatcherErrorEvent } from '../../../common/watcher.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; @@ -492,7 +492,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS try { // First check for symbolic link - realPath = await realpath(request.path); + realPath = await Promises.realpath(request.path); // Second check for casing difference // Note: this will be a no-op on Linux platforms diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 86099cc8b66..77950178326 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -119,7 +119,7 @@ export class InstantiationService implements IInstantiationService { this._throwIfDisposed(); let _trace: Trace; - let result: any; + let result: unknown; if (ctorOrDescriptor instanceof SyncDescriptor) { _trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor.ctor); result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace); diff --git a/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts similarity index 97% rename from src/vs/platform/ipc/electron-sandbox/mainProcessService.ts rename to src/vs/platform/ipc/electron-browser/mainProcessService.ts index 8707d26a237..618d8f70ef4 100644 --- a/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -5,7 +5,7 @@ import { Disposable } from '../../../base/common/lifecycle.js'; import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; -import { Client as IPCElectronClient } from '../../../base/parts/ipc/electron-sandbox/ipc.electron.js'; +import { Client as IPCElectronClient } from '../../../base/parts/ipc/electron-browser/ipc.electron.js'; import { IMainProcessService } from '../common/mainProcessService.js'; /** diff --git a/src/vs/platform/ipc/electron-sandbox/services.ts b/src/vs/platform/ipc/electron-browser/services.ts similarity index 100% rename from src/vs/platform/ipc/electron-sandbox/services.ts rename to src/vs/platform/ipc/electron-browser/services.ts diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index ee26e7b40b9..7678f6c893f 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { IME } from '../../../base/common/ime.js'; import { KeyCode } from '../../../base/common/keyCodes.js'; import { Keybinding, ResolvedChord, ResolvedKeybinding, SingleModifierChord } from '../../../base/common/keybindings.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; +import { Disposable, IDisposable } from '../../../base/common/lifecycle.js'; import * as nls from '../../../nls.js'; import { ICommandService } from '../../commands/common/commands.js'; @@ -89,7 +89,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[]; - public abstract registerSchemaContribution(contribution: KeybindingsSchemaContribution): void; + public abstract registerSchemaContribution(contribution: KeybindingsSchemaContribution): IDisposable; public abstract _dumpDebugInfo(): string; public abstract _dumpDebugInfoJSON(): string; diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 5dadf9b8e10..72f0d6c9640 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -7,6 +7,7 @@ import { Event } from '../../../base/common/event.js'; import { IJSONSchema } from '../../../base/common/jsonSchema.js'; import { KeyCode } from '../../../base/common/keyCodes.js'; import { ResolvedKeybinding, Keybinding } from '../../../base/common/keybindings.js'; +import { IDisposable } from '../../../base/common/lifecycle.js'; import { IContextKeyService, IContextKeyServiceTarget } from '../../contextkey/common/contextkey.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; import { ResolutionResult } from './keybindingResolver.js'; @@ -101,7 +102,7 @@ export interface IKeybindingService { */ mightProducePrintableCharacter(event: IKeyboardEvent): boolean; - registerSchemaContribution(contribution: KeybindingsSchemaContribution): void; + registerSchemaContribution(contribution: KeybindingsSchemaContribution): IDisposable; toggleLogging(): boolean; diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 075a5f5494c..b8ac90774ed 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { createSimpleKeybinding, ResolvedKeybinding, KeyCodeChord, Keybinding } from '../../../../base/common/keybindings.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { OS } from '../../../../base/common/platform.js'; import Severity from '../../../../base/common/severity.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; @@ -93,8 +93,8 @@ suite('AbstractKeybindingService', () => { return ''; } - public registerSchemaContribution() { - // noop + public registerSchemaContribution(): IDisposable { + return Disposable.None; } public enableKeybindingHoldMode() { diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 477ccb95290..b6263c56417 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from '../../../../base/common/event.js'; -import { ResolvedKeybinding, KeyCodeChord, Keybinding } from '../../../../base/common/keybindings.js'; +import { KeyCodeChord, Keybinding, ResolvedKeybinding } from '../../../../base/common/keybindings.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; import { OS } from '../../../../base/common/platform.js'; import { ContextKeyExpression, ContextKeyValue, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IScopedContextKeyService } from '../../../contextkey/common/contextkey.js'; import { IKeybindingService, IKeyboardEvent } from '../../common/keybinding.js'; @@ -168,6 +169,6 @@ export class MockKeybindingService implements IKeybindingService { } public registerSchemaContribution() { - // noop + return Disposable.None; } } diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 4912966c79c..30456f29d99 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -10,7 +10,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; import { isMacintosh, isWindows } from '../../../base/common/platform.js'; import { cwd } from '../../../base/common/process.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { NativeParsedArgs } from '../../environment/common/argv.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; import { ILogService } from '../../log/common/log.js'; @@ -428,7 +428,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe windowListeners.add(window.onWillLoad(e => this._onWillLoadWindow.fire({ window, workspace: e.workspace, reason: e.reason }))); // Window Before Closing: Main -> Renderer - const win = assertIsDefined(window.win); + const win = assertReturnsDefined(window.win); windowListeners.add(Event.fromNodeEventEmitter(win, 'close')(e => { // The window already acknowledged to be closed @@ -478,7 +478,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe } registerAuxWindow(auxWindow: IAuxiliaryWindow): void { - const win = assertIsDefined(auxWindow.win); + const win = assertReturnsDefined(auxWindow.win); const windowListeners = new DisposableStore(); windowListeners.add(Event.fromNodeEventEmitter(win, 'close')(e => { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 94c2acead57..27b1a2be4da 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -646,13 +646,6 @@ export class WorkbenchTable extends Table { } } -export interface IOpenResourceOptions { - editorOptions: IEditorOptions; - sideBySide: boolean; - element: any; - payload: any; -} - export interface IOpenEvent { editorOptions: IEditorOptions; sideBySide: boolean; diff --git a/src/vs/platform/menubar/electron-sandbox/menubar.ts b/src/vs/platform/menubar/electron-browser/menubar.ts similarity index 100% rename from src/vs/platform/menubar/electron-sandbox/menubar.ts rename to src/vs/platform/menubar/electron-browser/menubar.ts diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index d71c1930a3d..f3d2fbc9a57 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -16,7 +16,6 @@ import { dirname, join, posix, resolve, win32 } from '../../../base/common/path. import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; import { AddFirstParameterToFunctions } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; -import { realpath } from '../../../base/node/extpath.js'; import { virtualMachineHint } from '../../../base/node/id.js'; import { Promises, SymlinkSupport } from '../../../base/node/pfs.js'; import { findFreePort, isPortFree } from '../../../base/node/ports.js'; @@ -384,7 +383,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain try { const { symbolicLink } = await SymlinkSupport.stat(source); if (symbolicLink && !symbolicLink.dangling) { - const linkTargetRealPath = await realpath(source); + const linkTargetRealPath = await Promises.realpath(source); if (target === linkTargetRealPath) { return; } @@ -558,9 +557,9 @@ export class NativeHostMainService extends Disposable implements INativeHostMain this.environmentMainService.unsetSnapExportedVariables(); try { if (matchesSomeScheme(url, Schemas.http, Schemas.https)) { - this.openExternalBrowser(url, defaultApplication); + this.openExternalBrowser(windowId, url, defaultApplication); } else { - shell.openExternal(url); + this.doOpenShellExternal(windowId, url); } } finally { this.environmentMainService.restoreSnapExportedVariables(); @@ -569,28 +568,28 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return true; } - private async openExternalBrowser(url: string, defaultApplication?: string): Promise { + private async openExternalBrowser(windowId: number | undefined, url: string, defaultApplication?: string): Promise { const configuredBrowser = defaultApplication ?? this.configurationService.getValue('workbench.externalBrowser'); if (!configuredBrowser) { - return shell.openExternal(url); + return this.doOpenShellExternal(windowId, url); } if (configuredBrowser.includes(posix.sep) || configuredBrowser.includes(win32.sep)) { const browserPathExists = await Promises.exists(configuredBrowser); if (!browserPathExists) { this.logService.error(`Configured external browser path does not exist: ${configuredBrowser}`); - return shell.openExternal(url); + return this.doOpenShellExternal(windowId, url); } } try { - const { default: open } = await import('open'); + const { default: open, apps } = await import('open'); const res = await open(url, { app: { // Use `open.apps` helper to allow cross-platform browser // aliases to be looked up properly. Fallback to the // configured value if not found. - name: Object.hasOwn(open.apps, configuredBrowser) ? open.apps[(configuredBrowser as keyof typeof open['apps'])] : configuredBrowser + name: Object.hasOwn(apps, configuredBrowser) ? apps[(configuredBrowser as keyof typeof apps)] : configuredBrowser } }); @@ -602,12 +601,46 @@ export class NativeHostMainService extends Disposable implements INativeHostMain // (see also https://github.com/microsoft/vscode/issues/230636) res.stderr?.once('data', (data: Buffer) => { this.logService.error(`Error openening external URL '${url}' using browser '${configuredBrowser}': ${data.toString()}`); - return shell.openExternal(url); + return this.doOpenShellExternal(windowId, url); }); } } catch (error) { this.logService.error(`Unable to open external URL '${url}' using browser '${configuredBrowser}' due to ${error}.`); - return shell.openExternal(url); + return this.doOpenShellExternal(windowId, url); + } + } + + private async doOpenShellExternal(windowId: number | undefined, url: string): Promise { + try { + await shell.openExternal(url); + } catch (error) { + let isLink: boolean; + let message: string; + if (matchesSomeScheme(url, Schemas.http, Schemas.https)) { + isLink = true; + message = localize('openExternalErrorLinkMessage', "An error occurred opening a link in your default browser."); + } else { + isLink = false; + message = localize('openExternalProgramErrorMessage', "An error occurred opening an external program."); + } + + const { response } = await this.dialogMainService.showMessageBox({ + type: 'error', + message, + detail: error.message, + buttons: isLink ? [ + localize({ key: 'copyLink', comment: ['&& denotes a mnemonic'] }, "&&Copy Link"), + localize('cancel', "Cancel") + ] : [ + localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK") + ] + }, this.windowById(windowId)?.win ?? undefined); + + if (response === 1 /* Cancel */) { + return; + } + + this.writeClipboardText(windowId, url); } } diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index f1101520856..a2f59a71fab 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from '../../../nls.js'; import { IAction } from '../../../base/common/actions.js'; import { Event } from '../../../base/common/event.js'; import BaseSeverity from '../../../base/common/severity.js'; @@ -472,3 +473,19 @@ export class NoOpProgress implements INotificationProgress { total(value: number): void { } worked(value: number): void { } } + +export function withSeverityPrefix(label: string, severity: Severity): string { + + // Add severity prefix to match WCAG 4.1.3 Status + // Messages requirements. + + if (severity === Severity.Error) { + return localize('severityPrefix.error', "Error: {0}", label); + } + + if (severity === Severity.Warning) { + return localize('severityPrefix.warning', "Warning: {0}", label); + } + + return localize('severityPrefix.info', "Info: {0}", label); +} diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index d27cdce900f..a299f020c6e 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -60,7 +60,7 @@ else { // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.95.0-dev', + version: '1.102.0-dev', nameShort: 'Code - OSS Dev', nameLong: 'Code - OSS Dev', applicationName: 'code-oss', diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts b/src/vs/platform/profiling/electron-browser/profileAnalysisWorker.ts similarity index 100% rename from src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts rename to src/vs/platform/profiling/electron-browser/profileAnalysisWorker.ts diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerMain.ts b/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerMain.ts similarity index 100% rename from src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerMain.ts rename to src/vs/platform/profiling/electron-browser/profileAnalysisWorkerMain.ts diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts b/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts similarity index 97% rename from src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts rename to src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts index b53efdadc1a..1edace6dcaf 100644 --- a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts +++ b/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts @@ -49,7 +49,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { private async _withWorker(callback: (worker: Proxied) => Promise): Promise { const worker = createWebWorker( - FileAccess.asBrowserUri('vs/platform/profiling/electron-sandbox/profileAnalysisWorkerMain.js'), + FileAccess.asBrowserUri('vs/platform/profiling/electron-browser/profileAnalysisWorkerMain.js'), 'CpuProfileAnalysisWorker' ); diff --git a/src/vs/platform/profiling/electron-sandbox/profilingService.ts b/src/vs/platform/profiling/electron-browser/profilingService.ts similarity index 95% rename from src/vs/platform/profiling/electron-sandbox/profilingService.ts rename to src/vs/platform/profiling/electron-browser/profilingService.ts index ffb1e47edf1..54936f2c001 100644 --- a/src/vs/platform/profiling/electron-sandbox/profilingService.ts +++ b/src/vs/platform/profiling/electron-browser/profilingService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSharedProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../ipc/electron-browser/services.js'; import { IV8InspectProfilingService } from '../common/profiling.js'; registerSharedProcessRemoteService(IV8InspectProfilingService, 'v8InspectProfiling'); diff --git a/src/vs/platform/quickinput/browser/quickPickPin.ts b/src/vs/platform/quickinput/browser/quickPickPin.ts index 3d5b87e6e65..32f8c2d38e4 100644 --- a/src/vs/platform/quickinput/browser/quickPickPin.ts +++ b/src/vs/platform/quickinput/browser/quickPickPin.ts @@ -77,7 +77,7 @@ function _formatPinnedItems(storageKey: string, quickPick: IQuickPick; readonly onOpen: Event; readonly onClose: Event; - readonly onError: Event; + readonly onError: Event; - traceSocketEvent?(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void; + traceSocketEvent?(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void; send(data: ArrayBuffer | ArrayBufferView): void; close(): void; } @@ -58,7 +58,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { private readonly _onClose = this._register(new Emitter()); public readonly onClose = this._onClose.event; - private readonly _onError = this._register(new Emitter()); + private readonly _onError = this._register(new Emitter()); public readonly onError = this._onError.event; private readonly _debugLabel: string; @@ -127,7 +127,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { // delay the error event processing in the hope of receiving a close event // with more information - let pendingErrorEvent: any | null = null; + let pendingErrorEvent: unknown | null = null; const sendPendingErrorNow = () => { const err = pendingErrorEvent; @@ -137,13 +137,13 @@ class BrowserWebSocket extends Disposable implements IWebSocket { const errorRunner = this._register(new RunOnceScheduler(sendPendingErrorNow, 0)); - const sendErrorSoon = (err: any) => { + const sendErrorSoon = (err: unknown) => { errorRunner.cancel(); pendingErrorEvent = err; errorRunner.schedule(); }; - const sendErrorNow = (err: any) => { + const sendErrorNow = (err: unknown) => { errorRunner.cancel(); pendingErrorEvent = err; sendPendingErrorNow(); diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 938bbd80c48..56fb35c3f67 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -120,11 +120,11 @@ export class RemoteAuthorityResolverError extends ErrorNoTelemetry { public readonly _message: string | undefined; public readonly _code: RemoteAuthorityResolverErrorCode; - public readonly _detail: any; + public readonly _detail: unknown; public isHandled: boolean; - constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: any) { + constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: unknown) { super(message); this._message = message; diff --git a/src/vs/platform/remote/electron-sandbox/electronRemoteResourceLoader.ts b/src/vs/platform/remote/electron-browser/electronRemoteResourceLoader.ts similarity index 100% rename from src/vs/platform/remote/electron-sandbox/electronRemoteResourceLoader.ts rename to src/vs/platform/remote/electron-browser/electronRemoteResourceLoader.ts diff --git a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts similarity index 100% rename from src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts rename to src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts diff --git a/src/vs/platform/remote/electron-sandbox/sharedProcessTunnelService.ts b/src/vs/platform/remote/electron-browser/sharedProcessTunnelService.ts similarity index 95% rename from src/vs/platform/remote/electron-sandbox/sharedProcessTunnelService.ts rename to src/vs/platform/remote/electron-browser/sharedProcessTunnelService.ts index 81c04b34fcb..89e2e4034b0 100644 --- a/src/vs/platform/remote/electron-sandbox/sharedProcessTunnelService.ts +++ b/src/vs/platform/remote/electron-browser/sharedProcessTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSharedProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../ipc/electron-browser/services.js'; import { ISharedProcessTunnelService, ipcSharedProcessTunnelChannelName } from '../common/sharedProcessTunnelService.js'; registerSharedProcessRemoteService(ISharedProcessTunnelService, ipcSharedProcessTunnelChannelName); diff --git a/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts b/src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts similarity index 93% rename from src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts rename to src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts index ebf023ac49c..c085559fa6a 100644 --- a/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts +++ b/src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts @@ -8,7 +8,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c import product from '../../../product/common/product.js'; import { IProductService } from '../../../product/common/productService.js'; import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from '../../common/remoteAuthorityResolver.js'; -import { RemoteAuthorityResolverService } from '../../electron-sandbox/remoteAuthorityResolverService.js'; +import { RemoteAuthorityResolverService } from '../../electron-browser/remoteAuthorityResolverService.js'; suite('RemoteAuthorityResolverService', () => { diff --git a/src/vs/platform/remoteTunnel/electron-sandbox/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts similarity index 95% rename from src/vs/platform/remoteTunnel/electron-sandbox/remoteTunnelService.ts rename to src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts index 7aa3657e2a5..fc033ed547e 100644 --- a/src/vs/platform/remoteTunnel/electron-sandbox/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSharedProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../ipc/electron-browser/services.js'; import { IRemoteTunnelService } from '../common/remoteTunnel.js'; registerSharedProcessRemoteService(IRemoteTunnelService, 'remoteTunnel'); diff --git a/src/vs/platform/secrets/test/common/testSecretStorageService.ts b/src/vs/platform/secrets/test/common/testSecretStorageService.ts index ec7482e72bb..fa3d5483507 100644 --- a/src/vs/platform/secrets/test/common/testSecretStorageService.ts +++ b/src/vs/platform/secrets/test/common/testSecretStorageService.ts @@ -33,4 +33,9 @@ export class TestSecretStorageService implements ISecretStorageService { clear(): void { this._storage.clear(); } + + dispose(): void { + this._onDidChangeSecretEmitter.dispose(); + this._storage.clear(); + } } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 324779023c3..a9b7f5a4e47 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -17,7 +17,7 @@ import { ILoggerMainService } from '../../log/electron-main/loggerService.js'; import { UtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js'; import { NullTelemetryService } from '../../telemetry/common/telemetryUtils.js'; import { parseSharedProcessDebugPort } from '../../environment/node/environmentService.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { SharedProcessChannelConnection, SharedProcessRawConnection, SharedProcessLifecycle } from '../common/sharedProcess.js'; import { Emitter } from '../../../base/common/event.js'; @@ -202,7 +202,7 @@ export class SharedProcess extends Disposable { await this.whenIpcReady; // Connect and return message port - const utilityProcess = assertIsDefined(this.utilityProcess); + const utilityProcess = assertReturnsDefined(this.utilityProcess); return utilityProcess.connect(payload); } } diff --git a/src/vs/platform/shell/node/shellEnv.ts b/src/vs/platform/shell/node/shellEnv.ts index 29e28721dc3..ef8953444dc 100644 --- a/src/vs/platform/shell/node/shellEnv.ts +++ b/src/vs/platform/shell/node/shellEnv.ts @@ -104,8 +104,6 @@ async function doResolveShellEnv(logService: ILogService, token: CancellationTok logService.trace('doResolveShellEnv#noAttach', noAttach); const mark = generateUuid().replace(/-/g, '').substr(0, 12); - const regex = new RegExp(mark + '([\\s\\S]*?)' + mark); - const env = { ...process.env, ELECTRON_RUN_AS_NODE: '1', @@ -178,6 +176,9 @@ async function doResolveShellEnv(logService: ILogService, token: CancellationTok }); token.onCancellationRequested(() => { + logService.error('doResolveShellEnv#timeout', 'Shell environment resolution timed out, buffers so far:'); + logService.error('doResolveShellEnv#stdout', Buffer.concat(buffers).toString('utf8') || ''); + logService.error('doResolveShellEnv#stderr', Buffer.concat(stderr).toString('utf8') || ''); child.kill(); return reject(new CancellationError()); @@ -207,8 +208,9 @@ async function doResolveShellEnv(logService: ILogService, token: CancellationTok return reject(new Error(localize('resolveShellEnvExitError', "Unexpected exit code from spawned shell (code {0}, signal {1})", code, signal))); } - const match = regex.exec(raw); - const rawStripped = match ? match[1] : '{}'; + const startIndex = raw.indexOf(mark); + const endIndex = raw.lastIndexOf(mark); + const rawStripped = startIndex !== -1 && endIndex !== -1 && startIndex < endIndex ? raw.substring(startIndex + mark.length, endIndex).trim() : '{}'; try { const env = JSON.parse(rawStripped); diff --git a/src/vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService.ts b/src/vs/platform/telemetry/electron-browser/customEndpointTelemetryService.ts similarity index 95% rename from src/vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService.ts rename to src/vs/platform/telemetry/electron-browser/customEndpointTelemetryService.ts index 97a41b21d5b..7b557705a69 100644 --- a/src/vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService.ts +++ b/src/vs/platform/telemetry/electron-browser/customEndpointTelemetryService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSharedProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../ipc/electron-browser/services.js'; import { ICustomEndpointTelemetryService } from '../common/telemetry.js'; registerSharedProcessRemoteService(ICustomEndpointTelemetryService, 'customEndpointTelemetry'); diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 505e1f8ce46..bb62e2ee8f0 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -4,41 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from '../../../../base/common/event.js'; -import { IDisposable } from '../../../../base/common/lifecycle.js'; import type { IPromptInputModel, ISerializedPromptInputModel } from './commandDetection/promptInputModel.js'; import { ICurrentPartialCommand } from './commandDetection/terminalCommand.js'; import { ITerminalOutputMatch, ITerminalOutputMatcher } from '../terminal.js'; import { ReplayEntry } from '../terminalProcess.js'; - -interface IEvent { - (listener: (arg1: T, arg2: U) => any): IDisposable; -} - -export interface IMarker extends IDisposable { - /** - * A unique identifier for this marker. - */ - readonly id: number; - - /** - * Whether this marker is disposed. - */ - readonly isDisposed: boolean; - - /** - * The actual line index in the buffer at this point in time. This is set to - * -1 if the marker has been disposed. - */ - readonly line: number; - - /** - * Event listener to get notified when the marker gets disposed. Automatic disposal - * might happen for a marker, that got invalidated by scrolling out or removal of - * a line from the buffer. - */ - onDispose: IEvent; -} - +import type { IMarker } from '@xterm/headless'; /** * Primarily driven by the shell integration feature, a terminal capability is the mechanism for @@ -283,8 +253,8 @@ export interface INaiveCwdDetectionCapability { export interface IPartialCommandDetectionCapability { readonly type: TerminalCapability.PartialCommandDetection; - readonly commands: readonly IXtermMarker[]; - readonly onCommandFinished: Event; + readonly commands: readonly IMarker[]; + readonly onCommandFinished: Event; } interface IBaseTerminalCommand { @@ -307,9 +277,9 @@ interface IBaseTerminalCommand { export interface ITerminalCommand extends IBaseTerminalCommand { // Optional non-serializable readonly promptStartMarker?: IMarker; - readonly marker?: IXtermMarker; - endMarker?: IXtermMarker; - readonly executedMarker?: IXtermMarker; + readonly marker?: IMarker; + endMarker?: IMarker; + readonly executedMarker?: IMarker; readonly aliases?: string[][]; readonly wasReplayed?: boolean; @@ -332,15 +302,15 @@ export interface ISerializedTerminalCommand extends IBaseTerminalCommand { /** * A clone of the IMarker from xterm which cannot be imported from common */ -export interface IXtermMarker { - readonly id: number; - readonly isDisposed: boolean; - readonly line: number; - dispose(): void; - onDispose: { - (listener: () => any): { dispose(): void }; - }; -} +// export interface IMarker { +// readonly id: number; +// readonly isDisposed: boolean; +// readonly line: number; +// dispose(): void; +// onDispose: { +// (listener: () => any): { dispose(): void }; +// }; +// } export interface IMarkProperties { hoverMessage?: string; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts index fc8c7655d5e..ecc7f984d5c 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMarkProperties, IMarker, ISerializedTerminalCommand, ITerminalCommand, IXtermMarker } from '../capabilities.js'; +import { IMarkProperties, ISerializedTerminalCommand, ITerminalCommand } from '../capabilities.js'; import { ITerminalOutputMatcher, ITerminalOutputMatch } from '../../terminal.js'; -import type { IBuffer, IBufferLine, Terminal } from '@xterm/headless'; +import type { IBuffer, IBufferLine, IMarker, Terminal } from '@xterm/headless'; export interface ITerminalCommandProperties { command: string; @@ -13,7 +13,7 @@ export interface ITerminalCommandProperties { isTrusted: boolean; timestamp: number; duration: number; - marker: IXtermMarker | undefined; + marker: IMarker | undefined; cwd: string | undefined; exitCode: number | undefined; commandStartLineContent: string | undefined; @@ -22,8 +22,8 @@ export interface ITerminalCommandProperties { startX: number | undefined; promptStartMarker?: IMarker | undefined; - endMarker?: IXtermMarker | undefined; - executedMarker?: IXtermMarker | undefined; + endMarker?: IMarker | undefined; + executedMarker?: IMarker | undefined; aliases?: string[][] | undefined; wasReplayed?: boolean | undefined; } @@ -38,7 +38,7 @@ export class TerminalCommand implements ITerminalCommand { get promptStartMarker() { return this._properties.promptStartMarker; } get marker() { return this._properties.marker; } get endMarker() { return this._properties.endMarker; } - set endMarker(value: IXtermMarker | undefined) { this._properties.endMarker = value; } + set endMarker(value: IMarker | undefined) { this._properties.endMarker = value; } get executedMarker() { return this._properties.executedMarker; } get aliases() { return this._properties.aliases; } get wasReplayed() { return this._properties.wasReplayed; } @@ -361,9 +361,9 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { function extractCommandLine( buffer: IBuffer, cols: number, - commandStartMarker: IXtermMarker | undefined, + commandStartMarker: IMarker | undefined, commandStartX: number | undefined, - commandExecutedMarker: IXtermMarker | undefined, + commandExecutedMarker: IMarker | undefined, commandExecutedX: number | undefined ): string { if (!commandStartMarker || !commandExecutedMarker || commandStartX === undefined || commandExecutedX === undefined) { diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 02415e9a3c3..a68f0487ebe 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -8,7 +8,7 @@ import { debounce } from '../../../../base/common/decorators.js'; import { Emitter } from '../../../../base/common/event.js'; import { Disposable, MandatoryMutableDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { ILogService } from '../../../log/common/log.js'; -import { CommandInvalidationReason, ICommandDetectionCapability, ICommandInvalidationRequest, IHandleCommandOptions, ISerializedCommandDetectionCapability, ISerializedTerminalCommand, ITerminalCommand, IXtermMarker, TerminalCapability } from './capabilities.js'; +import { CommandInvalidationReason, ICommandDetectionCapability, ICommandInvalidationRequest, IHandleCommandOptions, ISerializedCommandDetectionCapability, ISerializedTerminalCommand, ITerminalCommand, TerminalCapability } from './capabilities.js'; import { ITerminalOutputMatcher } from '../terminal.js'; import { ICurrentPartialCommand, PartialTerminalCommand, TerminalCommand } from './commandDetection/terminalCommand.js'; import { PromptInputModel, type IPromptInputModel } from './commandDetection/promptInputModel.js'; @@ -1047,6 +1047,6 @@ function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number return content; } -function cloneMarker(xterm: Terminal, marker: IXtermMarker, offset: number = 0): IXtermMarker | undefined { +function cloneMarker(xterm: Terminal, marker: IMarker, offset: number = 0): IMarker | undefined { return xterm.registerMarker(marker.line - (xterm.buffer.active.baseY + xterm.buffer.active.cursorY) + offset); } diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index a380ed4089e..a0c7cf0efca 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -330,6 +330,7 @@ export interface IPtyService { start(id: number): Promise; shutdown(id: number, immediate: boolean): Promise; input(id: number, data: string): Promise; + sendSignal(id: number, signal: string): Promise; resize(id: number, cols: number, rows: number): Promise; clearBuffer(id: number): Promise; getInitialCwd(id: number): Promise; @@ -786,6 +787,7 @@ export interface ITerminalChildProcess { */ shutdown(immediate: boolean): void; input(data: string): void; + sendSignal(signal: string): void; processBinary(data: string): Promise; resize(cols: number, rows: number): void; clearBuffer(): void | Promise; diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 621c9ef3220..86e0e53b6e9 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -248,6 +248,9 @@ export class PtyHostService extends Disposable implements IPtyHostService { input(id: number, data: string): Promise { return this._proxy.input(id, data); } + sendSignal(id: number, signal: string): Promise { + return this._proxy.sendSignal(id, signal); + } processBinary(id: number, data: string): Promise { return this._proxy.processBinary(id, data); } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 37daa894dff..a43f323716b 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -49,7 +49,7 @@ export function traceRpc(_target: any, key: string, descriptor: any) { if (this.traceRpcArgs.simulatedLatency) { await timeout(this.traceRpcArgs.simulatedLatency); } - let result: any; + let result: unknown; try { result = await fn.apply(this, args); } catch (e) { @@ -422,6 +422,10 @@ export class PtyService extends Disposable implements IPtyService { } } @traceRpc + async sendSignal(id: number, signal: string): Promise { + return this._throwIfNoPty(id).sendSignal(signal); + } + @traceRpc async processBinary(id: number, data: string): Promise { return this._throwIfNoPty(id).writeBinary(data); } @@ -856,6 +860,12 @@ class PersistentTerminalProcess extends Disposable { } return this._terminalProcess.input(data); } + sendSignal(signal: string): void { + if (this._inReplay) { + return; + } + return this._terminalProcess.sendSignal(signal); + } writeBinary(data: string): Promise { return this._terminalProcess.processBinary(data); } diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 2acd35de838..a601d5d9ae3 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -338,10 +338,10 @@ enum ShellIntegrationExecutable { const shellIntegrationArgs: Map = new Map(); // The try catch swallows execution policy errors in the case of the archive distributable -shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwsh, ['-noexit', '-command', 'try { . \"{0}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.ps1\" } catch {}{1}']); -shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwshLogin, ['-l', '-noexit', '-command', 'try { . \"{0}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.ps1\" } catch {}{1}']); -shellIntegrationArgs.set(ShellIntegrationExecutable.Pwsh, ['-noexit', '-command', '. "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1"{1}']); -shellIntegrationArgs.set(ShellIntegrationExecutable.PwshLogin, ['-l', '-noexit', '-command', '. "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1"']); +shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwsh, ['-noexit', '-command', 'try { Import-Module \"{0}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.psm1\" } catch {}{1}']); +shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwshLogin, ['-l', '-noexit', '-command', 'try { Import-Module \"{0}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.psm1\" } catch {}{1}']); +shellIntegrationArgs.set(ShellIntegrationExecutable.Pwsh, ['-noexit', '-command', 'Import-Module "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1"{1}']); +shellIntegrationArgs.set(ShellIntegrationExecutable.PwshLogin, ['-l', '-noexit', '-command', 'Import-Module "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1"']); shellIntegrationArgs.set(ShellIntegrationExecutable.Zsh, ['-i']); shellIntegrationArgs.set(ShellIntegrationExecutable.ZshLogin, ['-il']); shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh']); diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 97011f1739f..1227cb058af 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -469,6 +469,13 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._startWrite(); } + sendSignal(signal: string): void { + if (this._store.isDisposed || !this._ptyProcess) { + return; + } + this._ptyProcess.kill(signal); + } + async processBinary(data: string): Promise { this.input(data, true); } diff --git a/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts b/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts index be014833344..56b20a59f62 100644 --- a/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts @@ -48,8 +48,8 @@ suite('platform - terminalEnvironment', async () => { // These tests are only expected to work on Windows 10 build 18309 and above (getWindowsBuildNumber() < 18309 ? suite.skip : suite)('pwsh', async () => { const expectedPs1 = process.platform === 'win32' - ? `try { . "${repoRoot}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.ps1" } catch {}` - : `. "${repoRoot}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1"`; + ? `try { Import-Module "${repoRoot}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.psm1" } catch {}` + : `Import-Module "${repoRoot}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1"`; suite('should override args', async () => { const enabledExpectedResult = Object.freeze({ type: 'injection', diff --git a/src/vs/platform/theme/common/colors/editorColors.ts b/src/vs/platform/theme/common/colors/editorColors.ts index 662fc637dc8..b4064b3cb2d 100644 --- a/src/vs/platform/theme/common/colors/editorColors.ts +++ b/src/vs/platform/theme/common/colors/editorColors.ts @@ -29,6 +29,10 @@ export const editorStickyScrollBackground = registerColor('editorStickyScroll.ba editorBackground, nls.localize('editorStickyScrollBackground', "Background color of sticky scroll in the editor")); +export const editorStickyScrollGutterBackground = registerColor('editorStickyScrollGutter.background', + editorBackground, + nls.localize('editorStickyScrollGutterBackground', "Background color of the gutter part of sticky scroll in the editor")); + export const editorStickyScrollHoverBackground = registerColor('editorStickyScrollHover.background', { dark: '#2A2D2E', light: '#F0F0F0', hcDark: null, hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('editorStickyScrollHoverBackground', "Background color of sticky scroll on hover in the editor")); diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index d530126a31a..531fc6171e6 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -3,57 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import electron from 'electron'; -import { Emitter, Event } from '../../../base/common/event.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; -import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; -import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { Event } from '../../../base/common/event.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; -import { IStateService } from '../../state/node/state.js'; import { IPartsSplash } from '../common/themeService.js'; import { IColorScheme } from '../../window/common/window.js'; -import { ThemeTypeSelector } from '../common/theme.js'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from '../../workspace/common/workspace.js'; -import { coalesce } from '../../../base/common/arrays.js'; -import { getAllWindowsExcludingOffscreen } from '../../windows/electron-main/windows.js'; -import { ILogService } from '../../log/common/log.js'; - -// These default colors match our default themes -// editor background color ("Dark Modern", etc...) -const DEFAULT_BG_LIGHT = '#FFFFFF'; -const DEFAULT_BG_DARK = '#1F1F1F'; -const DEFAULT_BG_HC_BLACK = '#000000'; -const DEFAULT_BG_HC_LIGHT = '#FFFFFF'; - -const THEME_STORAGE_KEY = 'theme'; -const THEME_BG_STORAGE_KEY = 'themeBackground'; - -const THEME_WINDOW_SPLASH_KEY = 'windowSplash'; -const THEME_WINDOW_SPLASH_OVERRIDE_KEY = 'windowSplashWorkspaceOverride'; - -const AUXILIARYBAR_DEFAULT_VISIBILITY = 'workbench.secondarySideBar.defaultVisibility'; - -namespace ThemeSettings { - export const DETECT_COLOR_SCHEME = 'window.autoDetectColorScheme'; - export const DETECT_HC = 'window.autoDetectHighContrast'; - export const SYSTEM_COLOR_THEME = 'window.systemColorTheme'; -} - -interface IPartSplashOverrideWorkspaces { - [workspaceId: string]: { - sideBarVisible: boolean; - auxiliaryBarVisible: boolean; - }; -} - -interface IPartsSplashOverride { - layoutInfo: { - sideBarWidth: number; - auxiliaryBarWidth: number; - - workspaces: IPartSplashOverrideWorkspaces; - }; -} export const IThemeMainService = createDecorator('themeMainService'); @@ -70,340 +24,3 @@ export interface IThemeMainService { getColorScheme(): IColorScheme; } - -export class ThemeMainService extends Disposable implements IThemeMainService { - - declare readonly _serviceBrand: undefined; - - private static readonly DEFAULT_BAR_WIDTH = 300; - - private static readonly WORKSPACE_OVERRIDE_LIMIT = 50; - - private readonly _onDidChangeColorScheme = this._register(new Emitter()); - readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; - - constructor( - @IStateService private stateService: IStateService, - @IConfigurationService private configurationService: IConfigurationService, - @ILogService private logService: ILogService - ) { - super(); - - // System Theme - if (!isLinux) { - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ThemeSettings.SYSTEM_COLOR_THEME) || e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME)) { - this.updateSystemColorTheme(); - } - })); - } - this.updateSystemColorTheme(); - - // Color Scheme changes - this._register(Event.fromNodeEventEmitter(electron.nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme()))); - } - - private updateSystemColorTheme(): void { - if (isLinux || this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { - electron.nativeTheme.themeSource = 'system'; // only with `system` we can detect the system color scheme - } else { - switch (this.configurationService.getValue<'default' | 'auto' | 'light' | 'dark'>(ThemeSettings.SYSTEM_COLOR_THEME)) { - case 'dark': - electron.nativeTheme.themeSource = 'dark'; - break; - case 'light': - electron.nativeTheme.themeSource = 'light'; - break; - case 'auto': - switch (this.getPreferredBaseTheme() ?? this.getStoredBaseTheme()) { - case ThemeTypeSelector.VS: electron.nativeTheme.themeSource = 'light'; break; - case ThemeTypeSelector.VS_DARK: electron.nativeTheme.themeSource = 'dark'; break; - default: electron.nativeTheme.themeSource = 'system'; - } - break; - default: - electron.nativeTheme.themeSource = 'system'; - break; - } - } - } - - getColorScheme(): IColorScheme { - - // high contrast is reflected by the shouldUseInvertedColorScheme property - if (isWindows) { - if (electron.nativeTheme.shouldUseHighContrastColors) { - // shouldUseInvertedColorScheme is dark, !shouldUseInvertedColorScheme is light - return { dark: electron.nativeTheme.shouldUseInvertedColorScheme, highContrast: true }; - } - } - - // high contrast is set if one of shouldUseInvertedColorScheme or shouldUseHighContrastColors is set, - // reflecting the 'Invert colours' and `Increase contrast` settings in MacOS - else if (isMacintosh) { - if (electron.nativeTheme.shouldUseInvertedColorScheme || electron.nativeTheme.shouldUseHighContrastColors) { - return { dark: electron.nativeTheme.shouldUseDarkColors, highContrast: true }; - } - } - - // ubuntu gnome seems to have 3 states, light dark and high contrast - else if (isLinux) { - if (electron.nativeTheme.shouldUseHighContrastColors) { - return { dark: true, highContrast: true }; - } - } - - return { - dark: electron.nativeTheme.shouldUseDarkColors, - highContrast: false - }; - } - - getPreferredBaseTheme(): ThemeTypeSelector | undefined { - const colorScheme = this.getColorScheme(); - if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && colorScheme.highContrast) { - return colorScheme.dark ? ThemeTypeSelector.HC_BLACK : ThemeTypeSelector.HC_LIGHT; - } - - if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { - return colorScheme.dark ? ThemeTypeSelector.VS_DARK : ThemeTypeSelector.VS; - } - - return undefined; - } - - getBackgroundColor(): string { - const preferred = this.getPreferredBaseTheme(); - const stored = this.getStoredBaseTheme(); - - // If the stored theme has the same base as the preferred, we can return the stored background - if (preferred === undefined || preferred === stored) { - const storedBackground = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); - if (storedBackground) { - return storedBackground; - } - } - - // Otherwise we return the default background for the preferred base theme. If there's no preferred, use the stored one. - switch (preferred ?? stored) { - case ThemeTypeSelector.VS: return DEFAULT_BG_LIGHT; - case ThemeTypeSelector.HC_BLACK: return DEFAULT_BG_HC_BLACK; - case ThemeTypeSelector.HC_LIGHT: return DEFAULT_BG_HC_LIGHT; - default: return DEFAULT_BG_DARK; - } - } - - private getStoredBaseTheme(): ThemeTypeSelector { - const baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, ThemeTypeSelector.VS_DARK).split(' ')[0]; - switch (baseTheme) { - case ThemeTypeSelector.VS: return ThemeTypeSelector.VS; - case ThemeTypeSelector.HC_BLACK: return ThemeTypeSelector.HC_BLACK; - case ThemeTypeSelector.HC_LIGHT: return ThemeTypeSelector.HC_LIGHT; - default: return ThemeTypeSelector.VS_DARK; - } - } - - saveWindowSplash(windowId: number | undefined, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): void { - - // Update override as needed - const splashOverride = this.updateWindowSplashOverride(workspace, splash); - - // Update in storage - this.stateService.setItems(coalesce([ - { key: THEME_STORAGE_KEY, data: splash.baseTheme }, - { key: THEME_BG_STORAGE_KEY, data: splash.colorInfo.background }, - { key: THEME_WINDOW_SPLASH_KEY, data: splash }, - splashOverride ? { key: THEME_WINDOW_SPLASH_OVERRIDE_KEY, data: splashOverride } : undefined - ])); - - // Update in opened windows - if (typeof windowId === 'number') { - this.updateBackgroundColor(windowId, splash); - } - - // Update system theme - this.updateSystemColorTheme(); - } - - private updateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): IPartsSplashOverride | undefined { - let splashOverride: IPartsSplashOverride | undefined = undefined; - let changed = false; - if (workspace) { - splashOverride = { ...this.getWindowSplashOverride() }; // make a copy for modifications - - changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'sideBar'); - changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'auxiliaryBar') || changed; - } - - return changed ? splashOverride : undefined; - } - - private doUpdateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, splash: IPartsSplash, splashOverride: IPartsSplashOverride, part: 'sideBar' | 'auxiliaryBar'): boolean { - const currentWidth = part === 'sideBar' ? splash.layoutInfo?.sideBarWidth : splash.layoutInfo?.auxiliarySideBarWidth; - const overrideWidth = part === 'sideBar' ? splashOverride.layoutInfo.sideBarWidth : splashOverride.layoutInfo.auxiliaryBarWidth; - - // No layout info: remove override - let changed = false; - if (typeof currentWidth !== 'number') { - if (splashOverride.layoutInfo.workspaces[workspace.id]) { - delete splashOverride.layoutInfo.workspaces[workspace.id]; - changed = true; - } - - return changed; - } - - let workspaceOverride = splashOverride.layoutInfo.workspaces[workspace.id]; - if (!workspaceOverride) { - const workspaceEntries = Object.keys(splashOverride.layoutInfo.workspaces); - if (workspaceEntries.length >= ThemeMainService.WORKSPACE_OVERRIDE_LIMIT) { - delete splashOverride.layoutInfo.workspaces[workspaceEntries[0]]; - changed = true; - } - - workspaceOverride = { sideBarVisible: false, auxiliaryBarVisible: false }; - splashOverride.layoutInfo.workspaces[workspace.id] = workspaceOverride; - changed = true; - } - - // Part has width: update width & visibility override - if (currentWidth > 0) { - if (overrideWidth !== currentWidth) { - splashOverride.layoutInfo[part === 'sideBar' ? 'sideBarWidth' : 'auxiliaryBarWidth'] = currentWidth; - changed = true; - } - - switch (part) { - case 'sideBar': - if (!workspaceOverride.sideBarVisible) { - workspaceOverride.sideBarVisible = true; - changed = true; - } - break; - case 'auxiliaryBar': - if (!workspaceOverride.auxiliaryBarVisible) { - workspaceOverride.auxiliaryBarVisible = true; - changed = true; - } - break; - } - } - - // Part is hidden: update visibility override - else { - switch (part) { - case 'sideBar': - if (workspaceOverride.sideBarVisible) { - workspaceOverride.sideBarVisible = false; - changed = true; - } - break; - case 'auxiliaryBar': - if (workspaceOverride.auxiliaryBarVisible) { - workspaceOverride.auxiliaryBarVisible = false; - changed = true; - } - break; - } - } - - return changed; - } - - private updateBackgroundColor(windowId: number, splash: IPartsSplash): void { - for (const window of getAllWindowsExcludingOffscreen()) { - if (window.id === windowId) { - window.setBackgroundColor(splash.colorInfo.background); - break; - } - } - } - - getWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { - try { - return this.doGetWindowSplash(workspace); - } catch (error) { - this.logService.error('[theme main service] Failed to get window splash', error); - - return undefined; - } - } - - private doGetWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { - const partSplash = this.stateService.getItem(THEME_WINDOW_SPLASH_KEY); - if (!partSplash?.layoutInfo) { - return partSplash; // return early: overrides currently only apply to layout info - } - - const override = this.getWindowSplashOverride(); - - // Figure out side bar width based on workspace and overrides - let sideBarWidth: number; - if (workspace) { - if (override.layoutInfo.workspaces[workspace.id]?.sideBarVisible === false) { - sideBarWidth = 0; - } else { - sideBarWidth = override.layoutInfo.sideBarWidth || partSplash.layoutInfo.sideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; - } - } else { - sideBarWidth = 0; - } - - // Figure out auxiliary bar width based on workspace, configuration and overrides - const auxiliarySideBarDefaultVisibility = this.configurationService.getValue(AUXILIARYBAR_DEFAULT_VISIBILITY); - let auxiliarySideBarWidth: number; - if (workspace) { - const auxiliaryBarVisible = override.layoutInfo.workspaces[workspace.id]?.auxiliaryBarVisible; - if (auxiliaryBarVisible === true) { - auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; - } else if (auxiliaryBarVisible === false) { - auxiliarySideBarWidth = 0; - } else { - if (auxiliarySideBarDefaultVisibility === 'visible' || auxiliarySideBarDefaultVisibility === 'visibleInWorkspace') { - auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; - } else { - auxiliarySideBarWidth = 0; - } - } - } else { - auxiliarySideBarWidth = 0; // technically not true if configured 'visible', but we never store splash per empty window, so we decide on a default here - } - - return { - ...partSplash, - layoutInfo: { - ...partSplash.layoutInfo, - sideBarWidth, - auxiliarySideBarWidth - } - }; - } - - private getWindowSplashOverride(): IPartsSplashOverride { - let override = this.stateService.getItem(THEME_WINDOW_SPLASH_OVERRIDE_KEY); - - if (!override?.layoutInfo) { - override = { - layoutInfo: { - sideBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, - auxiliaryBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, - workspaces: {} - } - }; - } - - if (!override.layoutInfo.sideBarWidth) { - override.layoutInfo.sideBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; - } - - if (!override.layoutInfo.auxiliaryBarWidth) { - override.layoutInfo.auxiliaryBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; - } - - if (!override.layoutInfo.workspaces) { - override.layoutInfo.workspaces = {}; - } - - return override; - } -} diff --git a/src/vs/platform/theme/electron-main/themeMainServiceImpl.ts b/src/vs/platform/theme/electron-main/themeMainServiceImpl.ts new file mode 100644 index 00000000000..a888ae35bf1 --- /dev/null +++ b/src/vs/platform/theme/electron-main/themeMainServiceImpl.ts @@ -0,0 +1,393 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import electron from 'electron'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { IStateService } from '../../state/node/state.js'; +import { IPartsSplash } from '../common/themeService.js'; +import { IColorScheme } from '../../window/common/window.js'; +import { ThemeTypeSelector } from '../common/theme.js'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from '../../workspace/common/workspace.js'; +import { coalesce } from '../../../base/common/arrays.js'; +import { getAllWindowsExcludingOffscreen } from '../../windows/electron-main/windows.js'; +import { ILogService } from '../../log/common/log.js'; +import { IThemeMainService } from './themeMainService.js'; + +// These default colors match our default themes +// editor background color ("Dark Modern", etc...) +const DEFAULT_BG_LIGHT = '#FFFFFF'; +const DEFAULT_BG_DARK = '#1F1F1F'; +const DEFAULT_BG_HC_BLACK = '#000000'; +const DEFAULT_BG_HC_LIGHT = '#FFFFFF'; + +const THEME_STORAGE_KEY = 'theme'; +const THEME_BG_STORAGE_KEY = 'themeBackground'; + +const THEME_WINDOW_SPLASH_KEY = 'windowSplash'; +const THEME_WINDOW_SPLASH_OVERRIDE_KEY = 'windowSplashWorkspaceOverride'; + +const AUXILIARYBAR_DEFAULT_VISIBILITY = 'workbench.secondarySideBar.defaultVisibility'; + +namespace ThemeSettings { + export const DETECT_COLOR_SCHEME = 'window.autoDetectColorScheme'; + export const DETECT_HC = 'window.autoDetectHighContrast'; + export const SYSTEM_COLOR_THEME = 'window.systemColorTheme'; +} + +interface IPartSplashOverrideWorkspaces { + [workspaceId: string]: { + sideBarVisible: boolean; + auxiliaryBarVisible: boolean; + }; +} + +interface IPartsSplashOverride { + layoutInfo: { + sideBarWidth: number; + auxiliaryBarWidth: number; + + workspaces: IPartSplashOverrideWorkspaces; + }; +} + +export class ThemeMainService extends Disposable implements IThemeMainService { + + declare readonly _serviceBrand: undefined; + + private static readonly DEFAULT_BAR_WIDTH = 300; + + private static readonly WORKSPACE_OVERRIDE_LIMIT = 50; + + private readonly _onDidChangeColorScheme = this._register(new Emitter()); + readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; + + constructor( + @IStateService private stateService: IStateService, + @IConfigurationService private configurationService: IConfigurationService, + @ILogService private logService: ILogService + ) { + super(); + + // System Theme + if (!isLinux) { + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ThemeSettings.SYSTEM_COLOR_THEME) || e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME)) { + this.updateSystemColorTheme(); + } + })); + } + this.updateSystemColorTheme(); + + // Color Scheme changes + this._register(Event.fromNodeEventEmitter(electron.nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme()))); + } + + private updateSystemColorTheme(): void { + if (isLinux || this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { + electron.nativeTheme.themeSource = 'system'; // only with `system` we can detect the system color scheme + } else { + switch (this.configurationService.getValue<'default' | 'auto' | 'light' | 'dark'>(ThemeSettings.SYSTEM_COLOR_THEME)) { + case 'dark': + electron.nativeTheme.themeSource = 'dark'; + break; + case 'light': + electron.nativeTheme.themeSource = 'light'; + break; + case 'auto': + switch (this.getPreferredBaseTheme() ?? this.getStoredBaseTheme()) { + case ThemeTypeSelector.VS: electron.nativeTheme.themeSource = 'light'; break; + case ThemeTypeSelector.VS_DARK: electron.nativeTheme.themeSource = 'dark'; break; + default: electron.nativeTheme.themeSource = 'system'; + } + break; + default: + electron.nativeTheme.themeSource = 'system'; + break; + } + } + } + + getColorScheme(): IColorScheme { + + // high contrast is reflected by the shouldUseInvertedColorScheme property + if (isWindows) { + if (electron.nativeTheme.shouldUseHighContrastColors) { + // shouldUseInvertedColorScheme is dark, !shouldUseInvertedColorScheme is light + return { dark: electron.nativeTheme.shouldUseInvertedColorScheme, highContrast: true }; + } + } + + // high contrast is set if one of shouldUseInvertedColorScheme or shouldUseHighContrastColors is set, + // reflecting the 'Invert colours' and `Increase contrast` settings in MacOS + else if (isMacintosh) { + if (electron.nativeTheme.shouldUseInvertedColorScheme || electron.nativeTheme.shouldUseHighContrastColors) { + return { dark: electron.nativeTheme.shouldUseDarkColors, highContrast: true }; + } + } + + // ubuntu gnome seems to have 3 states, light dark and high contrast + else if (isLinux) { + if (electron.nativeTheme.shouldUseHighContrastColors) { + return { dark: true, highContrast: true }; + } + } + + return { + dark: electron.nativeTheme.shouldUseDarkColors, + highContrast: false + }; + } + + getPreferredBaseTheme(): ThemeTypeSelector | undefined { + const colorScheme = this.getColorScheme(); + if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && colorScheme.highContrast) { + return colorScheme.dark ? ThemeTypeSelector.HC_BLACK : ThemeTypeSelector.HC_LIGHT; + } + + if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { + return colorScheme.dark ? ThemeTypeSelector.VS_DARK : ThemeTypeSelector.VS; + } + + return undefined; + } + + getBackgroundColor(): string { + const preferred = this.getPreferredBaseTheme(); + const stored = this.getStoredBaseTheme(); + + // If the stored theme has the same base as the preferred, we can return the stored background + if (preferred === undefined || preferred === stored) { + const storedBackground = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); + if (storedBackground) { + return storedBackground; + } + } + + // Otherwise we return the default background for the preferred base theme. If there's no preferred, use the stored one. + switch (preferred ?? stored) { + case ThemeTypeSelector.VS: return DEFAULT_BG_LIGHT; + case ThemeTypeSelector.HC_BLACK: return DEFAULT_BG_HC_BLACK; + case ThemeTypeSelector.HC_LIGHT: return DEFAULT_BG_HC_LIGHT; + default: return DEFAULT_BG_DARK; + } + } + + private getStoredBaseTheme(): ThemeTypeSelector { + const baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, ThemeTypeSelector.VS_DARK).split(' ')[0]; + switch (baseTheme) { + case ThemeTypeSelector.VS: return ThemeTypeSelector.VS; + case ThemeTypeSelector.HC_BLACK: return ThemeTypeSelector.HC_BLACK; + case ThemeTypeSelector.HC_LIGHT: return ThemeTypeSelector.HC_LIGHT; + default: return ThemeTypeSelector.VS_DARK; + } + } + + saveWindowSplash(windowId: number | undefined, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): void { + + // Update override as needed + const splashOverride = this.updateWindowSplashOverride(workspace, splash); + + // Update in storage + this.stateService.setItems(coalesce([ + { key: THEME_STORAGE_KEY, data: splash.baseTheme }, + { key: THEME_BG_STORAGE_KEY, data: splash.colorInfo.background }, + { key: THEME_WINDOW_SPLASH_KEY, data: splash }, + splashOverride ? { key: THEME_WINDOW_SPLASH_OVERRIDE_KEY, data: splashOverride } : undefined + ])); + + // Update in opened windows + if (typeof windowId === 'number') { + this.updateBackgroundColor(windowId, splash); + } + + // Update system theme + this.updateSystemColorTheme(); + } + + private updateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined, splash: IPartsSplash): IPartsSplashOverride | undefined { + let splashOverride: IPartsSplashOverride | undefined = undefined; + let changed = false; + if (workspace) { + splashOverride = { ...this.getWindowSplashOverride() }; // make a copy for modifications + + changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'sideBar'); + changed = this.doUpdateWindowSplashOverride(workspace, splash, splashOverride, 'auxiliaryBar') || changed; + } + + return changed ? splashOverride : undefined; + } + + private doUpdateWindowSplashOverride(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, splash: IPartsSplash, splashOverride: IPartsSplashOverride, part: 'sideBar' | 'auxiliaryBar'): boolean { + const currentWidth = part === 'sideBar' ? splash.layoutInfo?.sideBarWidth : splash.layoutInfo?.auxiliarySideBarWidth; + const overrideWidth = part === 'sideBar' ? splashOverride.layoutInfo.sideBarWidth : splashOverride.layoutInfo.auxiliaryBarWidth; + + // No layout info: remove override + let changed = false; + if (typeof currentWidth !== 'number') { + if (splashOverride.layoutInfo.workspaces[workspace.id]) { + delete splashOverride.layoutInfo.workspaces[workspace.id]; + changed = true; + } + + return changed; + } + + let workspaceOverride = splashOverride.layoutInfo.workspaces[workspace.id]; + if (!workspaceOverride) { + const workspaceEntries = Object.keys(splashOverride.layoutInfo.workspaces); + if (workspaceEntries.length >= ThemeMainService.WORKSPACE_OVERRIDE_LIMIT) { + delete splashOverride.layoutInfo.workspaces[workspaceEntries[0]]; + changed = true; + } + + workspaceOverride = { sideBarVisible: false, auxiliaryBarVisible: false }; + splashOverride.layoutInfo.workspaces[workspace.id] = workspaceOverride; + changed = true; + } + + // Part has width: update width & visibility override + if (currentWidth > 0) { + if (overrideWidth !== currentWidth) { + splashOverride.layoutInfo[part === 'sideBar' ? 'sideBarWidth' : 'auxiliaryBarWidth'] = currentWidth; + changed = true; + } + + switch (part) { + case 'sideBar': + if (!workspaceOverride.sideBarVisible) { + workspaceOverride.sideBarVisible = true; + changed = true; + } + break; + case 'auxiliaryBar': + if (!workspaceOverride.auxiliaryBarVisible) { + workspaceOverride.auxiliaryBarVisible = true; + changed = true; + } + break; + } + } + + // Part is hidden: update visibility override + else { + switch (part) { + case 'sideBar': + if (workspaceOverride.sideBarVisible) { + workspaceOverride.sideBarVisible = false; + changed = true; + } + break; + case 'auxiliaryBar': + if (workspaceOverride.auxiliaryBarVisible) { + workspaceOverride.auxiliaryBarVisible = false; + changed = true; + } + break; + } + } + + return changed; + } + + private updateBackgroundColor(windowId: number, splash: IPartsSplash): void { + for (const window of getAllWindowsExcludingOffscreen()) { + if (window.id === windowId) { + window.setBackgroundColor(splash.colorInfo.background); + break; + } + } + } + + getWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { + try { + return this.doGetWindowSplash(workspace); + } catch (error) { + this.logService.error('[theme main service] Failed to get window splash', error); + + return undefined; + } + } + + private doGetWindowSplash(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): IPartsSplash | undefined { + const partSplash = this.stateService.getItem(THEME_WINDOW_SPLASH_KEY); + if (!partSplash?.layoutInfo) { + return partSplash; // return early: overrides currently only apply to layout info + } + + const override = this.getWindowSplashOverride(); + + // Figure out side bar width based on workspace and overrides + let sideBarWidth: number; + if (workspace) { + if (override.layoutInfo.workspaces[workspace.id]?.sideBarVisible === false) { + sideBarWidth = 0; + } else { + sideBarWidth = override.layoutInfo.sideBarWidth || partSplash.layoutInfo.sideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; + } + } else { + sideBarWidth = 0; + } + + // Figure out auxiliary bar width based on workspace, configuration and overrides + const auxiliarySideBarDefaultVisibility = this.configurationService.getValue(AUXILIARYBAR_DEFAULT_VISIBILITY); + let auxiliarySideBarWidth: number; + if (workspace) { + const auxiliaryBarVisible = override.layoutInfo.workspaces[workspace.id]?.auxiliaryBarVisible; + if (auxiliaryBarVisible === true) { + auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; + } else if (auxiliaryBarVisible === false) { + auxiliarySideBarWidth = 0; + } else { + if (auxiliarySideBarDefaultVisibility === 'visible' || auxiliarySideBarDefaultVisibility === 'visibleInWorkspace') { + auxiliarySideBarWidth = override.layoutInfo.auxiliaryBarWidth || partSplash.layoutInfo.auxiliarySideBarWidth || ThemeMainService.DEFAULT_BAR_WIDTH; + } else { + auxiliarySideBarWidth = 0; + } + } + } else { + auxiliarySideBarWidth = 0; // technically not true if configured 'visible', but we never store splash per empty window, so we decide on a default here + } + + return { + ...partSplash, + layoutInfo: { + ...partSplash.layoutInfo, + sideBarWidth, + auxiliarySideBarWidth + } + }; + } + + private getWindowSplashOverride(): IPartsSplashOverride { + let override = this.stateService.getItem(THEME_WINDOW_SPLASH_OVERRIDE_KEY); + + if (!override?.layoutInfo) { + override = { + layoutInfo: { + sideBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, + auxiliaryBarWidth: ThemeMainService.DEFAULT_BAR_WIDTH, + workspaces: {} + } + }; + } + + if (!override.layoutInfo.sideBarWidth) { + override.layoutInfo.sideBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; + } + + if (!override.layoutInfo.auxiliaryBarWidth) { + override.layoutInfo.auxiliaryBarWidth = ThemeMainService.DEFAULT_BAR_WIDTH; + } + + if (!override.layoutInfo.workspaces) { + override.layoutInfo.workspaces = {}; + } + + return override; + } +} diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts b/src/vs/platform/userDataProfile/electron-browser/userDataProfileStorageService.ts similarity index 100% rename from src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts rename to src/vs/platform/userDataProfile/electron-browser/userDataProfileStorageService.ts diff --git a/src/vs/platform/userDataSync/common/content.ts b/src/vs/platform/userDataSync/common/content.ts index 77a6f04dffa..3f3af755f14 100644 --- a/src/vs/platform/userDataSync/common/content.ts +++ b/src/vs/platform/userDataSync/common/content.ts @@ -8,7 +8,7 @@ import { setProperty } from '../../../base/common/jsonEdit.js'; import { FormattingOptions } from '../../../base/common/jsonFormatter.js'; -export function edit(content: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions): string { +export function edit(content: string, originalPath: JSONPath, value: unknown, formattingOptions: FormattingOptions): string { const edit = setProperty(content, originalPath, value, formattingOptions)[0]; if (edit) { content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index f3eeaba274c..0057aa793ac 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -6,7 +6,7 @@ import { IStringDictionary } from '../../../base/common/collections.js'; import { deepClone, equals } from '../../../base/common/objects.js'; import * as semver from '../../../base/common/semver/semver.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { IExtensionIdentifier } from '../../extensions/common/extensions.js'; import { ILocalSyncExtension, IRemoteSyncExtension, ISyncExtension } from './userDataSync.js'; @@ -118,7 +118,7 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: continue; } - const baseExtension = assertIsDefined(lastSyncExtensionsMap?.get(key)); + const baseExtension = assertReturnsDefined(lastSyncExtensionsMap?.get(key)); const wasAnInstalledExtensionDuringLastSync = lastSyncBuiltinExtensionsSet && !lastSyncBuiltinExtensionsSet.has(key) && baseExtension.installed; if (localExtension.installed && wasAnInstalledExtensionDuringLastSync /* It is an installed extension now and during last sync */) { // Installed extension is removed from remote. Remove it from local. @@ -132,7 +132,7 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: // Remotely added extension => does not exist in base and exist in remote for (const key of baseToRemote.added.values()) { - const remoteExtension = assertIsDefined(remoteExtensionsMap.get(key)); + const remoteExtension = assertReturnsDefined(remoteExtensionsMap.get(key)); const localExtension = localExtensionsMap.get(key); // Also exist in local @@ -156,8 +156,8 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: // Remotely updated extension => exist in base and remote for (const key of baseToRemote.updated.values()) { - const remoteExtension = assertIsDefined(remoteExtensionsMap.get(key)); - const baseExtension = assertIsDefined(lastSyncExtensionsMap?.get(key)); + const remoteExtension = assertReturnsDefined(remoteExtensionsMap.get(key)); + const baseExtension = assertReturnsDefined(lastSyncExtensionsMap?.get(key)); const localExtension = localExtensionsMap.get(key); // Also exist in local @@ -186,7 +186,7 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: if (baseToRemote.added.has(key)) { continue; } - newRemoteExtensionsMap.set(key, assertIsDefined(localExtensionsMap.get(key))); + newRemoteExtensionsMap.set(key, assertReturnsDefined(localExtensionsMap.get(key))); } // Locally updated extension => exist in base and local @@ -199,8 +199,8 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: if (baseToRemote.updated.has(key)) { continue; } - const localExtension = assertIsDefined(localExtensionsMap.get(key)); - const remoteExtension = assertIsDefined(remoteExtensionsMap.get(key)); + const localExtension = assertReturnsDefined(localExtensionsMap.get(key)); + const remoteExtension = assertReturnsDefined(remoteExtensionsMap.get(key)); // Update remotely newRemoteExtensionsMap.set(key, merge(key, localExtension, remoteExtension, localExtension)); } @@ -220,7 +220,7 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: continue; } // Skip if it is a builtin extension - if (!assertIsDefined(remoteExtensionsMap.get(key)).installed) { + if (!assertReturnsDefined(remoteExtensionsMap.get(key)).installed) { continue; } // Skip if last sync builtin extensions set is not available @@ -228,7 +228,7 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: continue; } // Skip if it was a builtin extension during last sync - if (lastSyncBuiltinExtensionsSet.has(key) || !assertIsDefined(lastSyncExtensionsMap?.get(key)).installed) { + if (lastSyncBuiltinExtensionsSet.has(key) || !assertReturnsDefined(lastSyncExtensionsMap?.get(key)).installed) { continue; } newRemoteExtensionsMap.delete(key); diff --git a/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts b/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts index 4dd6fbe6823..a1f1b54b5f1 100644 --- a/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts +++ b/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts @@ -13,7 +13,7 @@ import { IStringDictionary } from '../../../../base/common/collections.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { IUriIdentityService } from '../../../uriIdentity/common/uriIdentity.js'; import { IEnvironmentService } from '../../../environment/common/environment.js'; -import { isPromptOrInstructionsFile } from '../../../prompts/common/prompts.js'; + import { IUserDataProfile } from '../../../userDataProfile/common/userDataProfile.js'; import { IConfigurationService } from '../../../configuration/common/configuration.js'; import { areSame, IMergeResult as IPromptsMergeResult, merge } from './promptsMerge.js'; @@ -516,14 +516,12 @@ export class PromptsSynchronizer extends AbstractSynchroniser implements IUserDa } for (const entry of stat.children || []) { const resource = entry.resource; - - if (isPromptOrInstructionsFile(resource) === false) { - continue; + const path = resource.path; + if (['.prompt.md', '.instructions.md', '.chatmode.md'].some(ext => path.endsWith(ext))) { + const key = this.extUri.relativePath(this.promptsFolder, resource)!; + const content = await this.fileService.readFile(resource); + prompts[key] = content; } - - const key = this.extUri.relativePath(this.promptsFolder, resource)!; - const content = await this.fileService.readFile(resource); - prompts[key] = content; } return prompts; diff --git a/src/vs/platform/userDataSync/test/common/promptsSync/promptsSync.test.ts b/src/vs/platform/userDataSync/test/common/promptsSync/promptsSync.test.ts index e56673233f9..13fa994788b 100644 --- a/src/vs/platform/userDataSync/test/common/promptsSync/promptsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/promptsSync/promptsSync.test.ts @@ -54,7 +54,7 @@ suite('PromptsSync', () => { await client2.setUp(true); }); - test('• when prompts does not exist', async () => { + test('when prompts does not exist', async () => { const fileService = testClient.instantiationService.get(IFileService); const promptsResource = testClient.instantiationService.get(IUserDataProfilesService).defaultProfile.promptsHome; @@ -91,7 +91,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(server.requests, []); }); - test('• when prompt is created after first sync', async () => { + test('when prompt is created after first sync', async () => { await testObject.sync(await testClient.getResourceManifest()); await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, testClient); @@ -126,7 +126,7 @@ suite('PromptsSync', () => { ); }); - test('• first time sync - outgoing to server (no prompts)', async () => { + test('first time sync - outgoing to server (no prompts)', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, testClient); await updatePrompt('prompt1.prompt.md', PROMPT1_TEXT, testClient); @@ -149,7 +149,7 @@ suite('PromptsSync', () => { }); }); - test('• first time sync - incoming from server (no prompts)', async () => { + test('first time sync - incoming from server (no prompts)', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('prompt1.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -164,7 +164,7 @@ suite('PromptsSync', () => { assert.strictEqual(actual2, PROMPT1_TEXT); }); - test('• first time sync when prompts exists', async () => { + test('first time sync when prompts exists', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); @@ -193,7 +193,7 @@ suite('PromptsSync', () => { }); }); - test('• first time sync when prompts exists - has conflicts', async () => { + test('first time sync when prompts exists - has conflicts', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); @@ -212,7 +212,7 @@ suite('PromptsSync', () => { assertPreviews(testObject.conflicts.conflicts, [local]); }); - test('• first time sync when prompts exists - has conflicts and accept conflicts', async () => { + test('first time sync when prompts exists - has conflicts and accept conflicts', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); @@ -238,7 +238,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'prompt3.prompt.md': PROMPT3_TEXT }); }); - test('• first time sync when prompts exists - has multiple conflicts', async () => { + test('first time sync when prompts exists - has multiple conflicts', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('prompt1.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -254,7 +254,7 @@ suite('PromptsSync', () => { assertPreviews(testObject.conflicts.conflicts, [local1, local2]); }); - test('• first time sync when prompts exists - has multiple conflicts and accept one conflict', async () => { + test('first time sync when prompts exists - has multiple conflicts and accept one conflict', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('prompt1.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -273,7 +273,7 @@ suite('PromptsSync', () => { assertPreviews(testObject.conflicts.conflicts, [local]); }); - test('• first time sync when prompts exists - has multiple conflicts and accept all conflicts', async () => { + test('first time sync when prompts exists - has multiple conflicts and accept all conflicts', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('prompt1.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -305,7 +305,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'prompt3.prompt.md': PROMPT4_TEXT, 'prompt1.prompt.md': PROMPT1_TEXT }); }); - test('• sync adding a prompt', async () => { + test('sync adding a prompt', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, testClient); await testObject.sync(await testClient.getResourceManifest()); @@ -325,7 +325,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'prompt3.prompt.md': PROMPT3_TEXT, 'prompt1.prompt.md': PROMPT1_TEXT }); }); - test('• sync adding a prompt - accept', async () => { + test('sync adding a prompt - accept', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); await testObject.sync(await testClient.getResourceManifest()); @@ -343,7 +343,7 @@ suite('PromptsSync', () => { assert.strictEqual(actual2, PROMPT1_TEXT); }); - test('• sync updating a prompt', async () => { + test('sync updating a prompt', async () => { await updatePrompt('default.prompt.md', PROMPT3_TEXT, testClient); await testObject.sync(await testClient.getResourceManifest()); @@ -361,7 +361,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'default.prompt.md': PROMPT4_TEXT }); }); - test('• sync updating a prompt - accept', async () => { + test('sync updating a prompt - accept', async () => { await updatePrompt('my.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); await testObject.sync(await testClient.getResourceManifest()); @@ -377,7 +377,7 @@ suite('PromptsSync', () => { assert.strictEqual(actual1, PROMPT4_TEXT); }); - test('• sync updating a prompt - conflict', async () => { + test('sync updating a prompt - conflict', async () => { await updatePrompt('some.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); await testObject.sync(await testClient.getResourceManifest()); @@ -393,7 +393,7 @@ suite('PromptsSync', () => { assertPreviews(testObject.conflicts.conflicts, [local]); }); - test('• sync updating a prompt - resolve conflict', async () => { + test('sync updating a prompt - resolve conflict', async () => { await updatePrompt('advanced.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); await testObject.sync(await testClient.getResourceManifest()); @@ -418,7 +418,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'advanced.prompt.md': PROMPT4_TEXT }); }); - test('• sync removing a prompt', async () => { + test('sync removing a prompt', async () => { await updatePrompt('another.prompt.md', PROMPT3_TEXT, testClient); await updatePrompt('chat.prompt.md', PROMPT1_TEXT, testClient); await testObject.sync(await testClient.getResourceManifest()); @@ -443,7 +443,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'chat.prompt.md': PROMPT1_TEXT }); }); - test('• sync removing a prompt - accept', async () => { + test('sync removing a prompt - accept', async () => { await updatePrompt('my-query.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('summarize.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -462,7 +462,7 @@ suite('PromptsSync', () => { assert.strictEqual(actual2, null); }); - test('• sync removing a prompt locally and updating it remotely', async () => { + test('sync removing a prompt locally and updating it remotely', async () => { await updatePrompt('some.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('important.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -483,7 +483,7 @@ suite('PromptsSync', () => { assert.strictEqual(actual2, PROMPT4_TEXT); }); - test('• sync removing a prompt - conflict', async () => { + test('sync removing a prompt - conflict', async () => { await updatePrompt('common.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('rare.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -501,7 +501,7 @@ suite('PromptsSync', () => { assertPreviews(testObject.conflicts.conflicts, [local]); }); - test('• sync removing a prompt - resolve conflict', async () => { + test('sync removing a prompt - resolve conflict', async () => { await updatePrompt('uncommon.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('hot.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -533,7 +533,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'hot.prompt.md': PROMPT1_TEXT, 'uncommon.prompt.md': PROMPT5_TEXT }); }); - test('• sync removing a prompt - resolve conflict by removing', async () => { + test('sync removing a prompt - resolve conflict by removing', async () => { await updatePrompt('prompt3.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('refactor.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -565,7 +565,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'refactor.prompt.md': PROMPT1_TEXT }); }); - test('• sync prompts', async () => { + test('sync prompts', async () => { await updatePrompt('first.prompt.md', PROMPT6_TEXT, client2); await updatePrompt('roaming.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); @@ -589,7 +589,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'roaming.prompt.md': PROMPT3_TEXT, 'first.prompt.md': PROMPT6_TEXT }); }); - test('• sync should ignore non prompts', async () => { + test('sync should ignore non prompts', async () => { await updatePrompt('my.prompt.md', PROMPT6_TEXT, client2); await updatePrompt('html.html', PROMPT3_TEXT, client2); await updatePrompt('shared.prompt.md', PROMPT1_TEXT, client2); @@ -612,7 +612,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(actual, { 'shared.prompt.md': PROMPT1_TEXT, 'my.prompt.md': PROMPT6_TEXT }); }); - test('• previews are reset after all conflicts resolved', async () => { + test('previews are reset after all conflicts resolved', async () => { await updatePrompt('html.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('css.prompt.md', PROMPT1_TEXT, client2); await client2.sync(); @@ -628,7 +628,7 @@ suite('PromptsSync', () => { assert.ok(!await fileService.exists(dirname(conflicts[0].previewResource))); }); - test('• merge when there are multiple prompts and all prompts are merged', async () => { + test('merge when there are multiple prompts and all prompts are merged', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updatePrompt('sublime.prompt.md', PROMPT4_TEXT, testClient); @@ -644,7 +644,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('• merge when there are multiple prompts and all prompts are merged and applied', async () => { + test('merge when there are multiple prompts and all prompts are merged and applied', async () => { await updatePrompt('short.prompt.md', PROMPT4_TEXT, testClient); await updatePrompt('long.prompt.md', PROMPT2_TEXT, testClient); let preview = await testObject.sync(await testClient.getResourceManifest(), true); @@ -655,7 +655,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('• merge when there are multiple prompts and one prompt has no changes and one prompt is merged', async () => { + test('merge when there are multiple prompts and one prompt has no changes and one prompt is merged', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updatePrompt('coding.prompt.md', PROMPT3_TEXT, client2); @@ -674,7 +674,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('• merge when there are multiple prompts and one prompt has no changes and prompts is merged and applied', async () => { + test('merge when there are multiple prompts and one prompt has no changes and prompts is merged and applied', async () => { await updatePrompt('quick.prompt.md', PROMPT3_TEXT, client2); await client2.sync(); @@ -689,7 +689,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('• merge when there are multiple prompts with conflicts and all prompts are merged', async () => { + test('merge when there are multiple prompts with conflicts and all prompts are merged', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updatePrompt('reverse.prompt.md', PROMPT3_TEXT, client2); @@ -713,7 +713,7 @@ suite('PromptsSync', () => { ]); }); - test('• accept when there are multiple prompts with conflicts and only one prompt is accepted', async () => { + test('accept when there are multiple prompts with conflicts and only one prompt is accepted', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updatePrompt('current.prompt.md', PROMPT3_TEXT, client2); @@ -750,7 +750,7 @@ suite('PromptsSync', () => { ]); }); - test('• accept when there are multiple prompts with conflicts and all prompts are accepted', async () => { + test('accept when there are multiple prompts with conflicts and all prompts are accepted', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updatePrompt('dynamic.prompt.md', PROMPT3_TEXT, client2); @@ -785,7 +785,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('• accept when there are multiple prompts with conflicts and all prompts are accepted and applied', async () => { + test('accept when there are multiple prompts with conflicts and all prompts are accepted and applied', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updatePrompt('edicational.prompt.md', PROMPT3_TEXT, client2); await updatePrompt('unknown.prompt.md', PROMPT1_TEXT, client2); @@ -833,7 +833,7 @@ suite('PromptsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('• sync profile prompts', async () => { + test('sync profile prompts', async () => { const client2 = disposableStore.add(new UserDataSyncClient(server)); await client2.setUp(true); const profile = await client2.instantiationService.get(IUserDataProfilesService).createNamedProfile('profile1'); diff --git a/src/vs/platform/webContentExtractor/electron-sandbox/webContentExtractorService.ts b/src/vs/platform/webContentExtractor/electron-browser/webContentExtractorService.ts similarity index 91% rename from src/vs/platform/webContentExtractor/electron-sandbox/webContentExtractorService.ts rename to src/vs/platform/webContentExtractor/electron-browser/webContentExtractorService.ts index 2d6ebf12b5f..9fab2c6e7e1 100644 --- a/src/vs/platform/webContentExtractor/electron-sandbox/webContentExtractorService.ts +++ b/src/vs/platform/webContentExtractor/electron-browser/webContentExtractorService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerMainProcessRemoteService, registerSharedProcessRemoteService } from '../../ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService, registerSharedProcessRemoteService } from '../../ipc/electron-browser/services.js'; import { ISharedWebContentExtractorService, IWebContentExtractorService } from '../common/webContentExtractor.js'; registerMainProcessRemoteService(IWebContentExtractorService, 'webContentExtractor'); diff --git a/src/vs/platform/webContentExtractor/node/sharedWebContentExtractorService.ts b/src/vs/platform/webContentExtractor/node/sharedWebContentExtractorService.ts index 61ae28d1e9b..ae70e341c0c 100644 --- a/src/vs/platform/webContentExtractor/node/sharedWebContentExtractorService.ts +++ b/src/vs/platform/webContentExtractor/node/sharedWebContentExtractorService.ts @@ -28,7 +28,7 @@ export class SharedWebContentExtractorService implements ISharedWebContentExtrac return undefined; } - const content = VSBuffer.wrap(await response.bytes()); + const content = VSBuffer.wrap(await (response as unknown as { bytes: () => Promise> } /* workaround https://github.com/microsoft/TypeScript/issues/61826 */).bytes()); return content; } catch (err) { console.log(err); diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-browser/window.ts similarity index 98% rename from src/vs/platform/window/electron-sandbox/window.ts rename to src/vs/platform/window/electron-browser/window.ts index c9914e2117e..cdd43a4e00c 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-browser/window.ts @@ -7,7 +7,7 @@ import { getZoomLevel, setZoomFactor, setZoomLevel } from '../../../base/browser import { getActiveWindow, getWindows } from '../../../base/browser/dom.js'; import { mainWindow } from '../../../base/browser/window.js'; import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandboxTypes.js'; -import { ISandboxGlobals, ipcRenderer, webFrame } from '../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ISandboxGlobals, ipcRenderer, webFrame } from '../../../base/parts/sandbox/electron-browser/globals.js'; import { zoomLevelToZoomFactor } from '../common/window.js'; export enum ApplyZoomTarget { diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 4eae571a75c..31d0dc59843 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -150,6 +150,8 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this.dispose(); })); this._register(Event.fromNodeEventEmitter(win, 'focus')(() => { + this.clearFocusNotificationBadge(); + this._lastFocusTime = Date.now(); })); this._register(Event.fromNodeEventEmitter(this._win, 'enter-full-screen')(() => this._onDidEnterFullScreen.fire())); @@ -323,11 +325,21 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { case FocusMode.Notify: if (isMacintosh) { + this.setFocusNotificationBadge(undefined /* generic dot */); + + // On macOS we have direct API to bounce the dock icon electron.app.dock?.bounce('informational'); } else if (isWindows) { - // On Windows, this just flashes the taskbar icon, which is desired + this.setFocusNotificationBadge(undefined /* generic dot */); + + // On Windows, calling focus() will bounce the taskbar icon // https://github.com/electron/electron/issues/2867 this.win?.focus(); + } else if (isLinux) { + this.setFocusNotificationBadge(1 /* only number supported */); + + // On Linux, there seems to be no way to bounce the taskbar icon + // as calling focus() will actually steal focus away. } break; @@ -340,6 +352,20 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { } } + private hasFocusNotificationBadge = false; + + private setFocusNotificationBadge(count?: number): void { + electron.app.setBadgeCount(count); + this.hasFocusNotificationBadge = true; + } + + private clearFocusNotificationBadge(): void { + if (this.hasFocusNotificationBadge) { + electron.app.setBadgeCount(0); + this.hasFocusNotificationBadge = false; + } + } + private doFocusWindow() { const win = this.win; if (!win) { @@ -619,7 +645,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this.logService.trace('window#ctor: using window state', state); const options = instantiationService.invokeFunction(defaultBrowserWindowOptions, this.windowState, undefined, { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload.js').fsPath, + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js').fsPath, additionalArguments: [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', }); @@ -1091,7 +1117,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this.readyState = ReadyState.NAVIGATING; // Load URL - this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); + this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-browser/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); // Remember that we did load const wasLoaded = this.wasLoaded; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 594b8364353..6b9748620c9 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -20,7 +20,7 @@ import { getMarks, mark } from '../../../base/common/performance.js'; import { IProcessEnvironment, isMacintosh, isWindows, OS } from '../../../base/common/platform.js'; import { cwd } from '../../../base/common/process.js'; import { extUriBiasedIgnorePathCase, isEqualAuthority, normalizePath, originalFSPath, removeTrailingPathSeparator } from '../../../base/common/resources.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { getNLSLanguage, getNLSMessages, localize } from '../../../nls.js'; import { IBackupMainService } from '../../backup/electron-main/backup.js'; @@ -1555,7 +1555,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic disposables.add(createdWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire({ window: createdWindow, fullscreen: false }))); disposables.add(createdWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: createdWindow, x, y }))); - const webContents = assertIsDefined(createdWindow.win?.webContents); + const webContents = assertReturnsDefined(createdWindow.win?.webContents); webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own disposables.add(Event.fromNodeEventEmitter(webContents, 'devtools-reload-page')(() => this.lifecycleMainService.reload(createdWindow))); diff --git a/src/vs/server/node/remoteExtensionsScanner.ts b/src/vs/server/node/remoteExtensionsScanner.ts index 1bd6cf68c1d..bbc82fd06bc 100644 --- a/src/vs/server/node/remoteExtensionsScanner.ts +++ b/src/vs/server/node/remoteExtensionsScanner.ts @@ -11,7 +11,7 @@ import * as performance from '../../base/common/performance.js'; import { Event } from '../../base/common/event.js'; import { IURITransformer, transformOutgoingURIs } from '../../base/common/uriIpc.js'; import { IServerChannel } from '../../base/parts/ipc/common/ipc.js'; -import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyExpression, ContextKeyGreaterEqualsExpr, ContextKeyGreaterExpr, ContextKeyInExpr, ContextKeyNotEqualsExpr, ContextKeyNotExpr, ContextKeyNotInExpr, ContextKeyRegexExpr, ContextKeySmallerEqualsExpr, ContextKeySmallerExpr, IContextKeyExprMapper } from '../../platform/contextkey/common/contextkey.js'; +import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyExpression, ContextKeyGreaterEqualsExpr, ContextKeyGreaterExpr, ContextKeyInExpr, ContextKeyNotEqualsExpr, ContextKeyNotExpr, ContextKeyNotInExpr, ContextKeyRegexExpr, ContextKeySmallerEqualsExpr, ContextKeySmallerExpr, ContextKeyValue, IContextKeyExprMapper } from '../../platform/contextkey/common/contextkey.js'; import { IExtensionGalleryService, IExtensionManagementService, InstallExtensionSummary, InstallOptions } from '../../platform/extensionManagement/common/extensionManagement.js'; import { ExtensionManagementCLI } from '../../platform/extensionManagement/common/extensionManagementCLI.js'; import { IExtensionsScannerService, toExtensionDescription } from '../../platform/extensionManagement/common/extensionsScannerService.js'; @@ -238,30 +238,30 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS mapNot(key: string): ContextKeyExpression { return ContextKeyNotExpr.create(key); } - mapEquals(key: string, value: any): ContextKeyExpression { + mapEquals(key: string, value: ContextKeyValue): ContextKeyExpression { if (key === 'resourceScheme' && typeof value === 'string') { return ContextKeyEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); } else { return ContextKeyEqualsExpr.create(key, value); } } - mapNotEquals(key: string, value: any): ContextKeyExpression { + mapNotEquals(key: string, value: ContextKeyValue): ContextKeyExpression { if (key === 'resourceScheme' && typeof value === 'string') { return ContextKeyNotEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); } else { return ContextKeyNotEqualsExpr.create(key, value); } } - mapGreater(key: string, value: any): ContextKeyExpression { + mapGreater(key: string, value: ContextKeyValue): ContextKeyExpression { return ContextKeyGreaterExpr.create(key, value); } - mapGreaterEquals(key: string, value: any): ContextKeyExpression { + mapGreaterEquals(key: string, value: ContextKeyValue): ContextKeyExpression { return ContextKeyGreaterEqualsExpr.create(key, value); } - mapSmaller(key: string, value: any): ContextKeyExpression { + mapSmaller(key: string, value: ContextKeyValue): ContextKeyExpression { return ContextKeySmallerExpr.create(key, value); } - mapSmallerEquals(key: string, value: any): ContextKeyExpression { + mapSmallerEquals(key: string, value: ContextKeyValue): ContextKeyExpression { return ContextKeySmallerEqualsExpr.create(key, value); } mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr { diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts index 325e8db1c51..1cb1e36c5f0 100644 --- a/src/vs/server/node/remoteTerminalChannel.ts +++ b/src/vs/server/node/remoteTerminalChannel.ts @@ -128,6 +128,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< case RemoteTerminalChannelRequest.Start: return this._ptyHostService.start.apply(this._ptyHostService, args); case RemoteTerminalChannelRequest.Input: return this._ptyHostService.input.apply(this._ptyHostService, args); + case RemoteTerminalChannelRequest.SendSignal: return this._ptyHostService.sendSignal.apply(this._ptyHostService, args); case RemoteTerminalChannelRequest.AcknowledgeDataEvent: return this._ptyHostService.acknowledgeDataEvent.apply(this._ptyHostService, args); case RemoteTerminalChannelRequest.Shutdown: return this._ptyHostService.shutdown.apply(this._ptyHostService, args); case RemoteTerminalChannelRequest.Resize: return this._ptyHostService.resize.apply(this._ptyHostService, args); diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index d8e6acdcbc7..4545e3c2a8d 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -80,6 +80,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu private readonly _registrations = this._register(new DisposableMap()); private _sentProviderUsageEvents = new Set(); + private _suppressUnregisterEvent = false; constructor( extHostContext: IExtHostContext, @@ -101,7 +102,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); this._register(this.authenticationService.onDidChangeSessions(e => this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label))); - this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this._proxy.$onDidUnregisterAuthenticationProvider(e.id))); + this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => { + if (!this._suppressUnregisterEvent) { + this._proxy.$onDidUnregisterAuthenticationProvider(e.id); + } + })); this._register(this.authenticationExtensionsService.onDidChangeAccountPreference(e => { const providerInfo = this.authenticationService.getProvider(e.providerId); this._proxy.$onDidChangeAuthenticationSessions(providerInfo.id, providerInfo.label, e.extensionIds); @@ -116,7 +121,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // Prefer Node.js extension hosts when they're available. No CORS issues etc. priority: extHostContext.extensionHostKind === ExtensionHostKind.LocalWebWorker ? 0 : 1, create: async (authorizationServer, serverMetadata, resource) => { - const authProviderId = authorizationServer.toString(true); + // Auth Provider Id is a combination of the authorization server and the resource, if provided. + const authProviderId = resource ? `${authorizationServer.toString(true)} ${resource.resource}` : authorizationServer.toString(true); const clientId = this.dynamicAuthProviderStorageService.getClientId(authProviderId); let initialTokens: (IAuthorizationTokenResponse & { created_at: number })[] | undefined = undefined; if (clientId) { @@ -153,7 +159,13 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu async $unregisterAuthenticationProvider(id: string): Promise { this._registrations.deleteAndDispose(id); - this.authenticationService.unregisterAuthenticationProvider(id); + // The ext host side already unregisters the provider, so we can suppress the event here. + this._suppressUnregisterEvent = true; + try { + this.authenticationService.unregisterAuthenticationProvider(id); + } finally { + this._suppressUnregisterEvent = false; + } } async $ensureProvider(id: string): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index a43107d7502..557a2376556 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -12,6 +12,7 @@ import { revive } from '../../../base/common/marshalling.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; +import { Schemas } from '../../../base/common/network.js'; import { Position } from '../../../editor/common/core/position.js'; import { Range } from '../../../editor/common/core/range.js'; import { getWordAtText } from '../../../editor/common/core/wordHelper.js'; @@ -23,7 +24,6 @@ import { IInstantiationService } from '../../../platform/instantiation/common/in import { ILogService } from '../../../platform/log/common/log.js'; import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { IChatWidgetService } from '../../contrib/chat/browser/chat.js'; -import { ChatInputPart } from '../../contrib/chat/browser/chatInputPart.js'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/contrib/chatDynamicVariables.js'; import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/chatAgents.js'; import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/chatEditingService.js'; @@ -313,7 +313,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA }; this._agentIdsToCompletionProviders.set(id, this._chatAgentService.registerAgentCompletionProvider(id, provide)); - this._agentCompletionProviders.set(handle, this._languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._agentCompletionProviders.set(handle, this._languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentCompletions:' + handle, triggerCharacters, provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index 86891475af3..07c29c26ee9 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -45,7 +45,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise { + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: unknown, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise { overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; return this.writeConfiguration(target, key, value, overrides, scopeToLanguage); } @@ -55,7 +55,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { return this.writeConfiguration(target, key, undefined, overrides, scopeToLanguage); } - private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + private writeConfiguration(target: ConfigurationTarget | null, key: string, value: unknown, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, overrides); const configurationValue = this.configurationService.inspect(key, overrides); switch (target) { @@ -72,7 +72,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { } } - private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + private _updateValue(key: string, value: unknown, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { overrides = scopeToLanguage === true ? overrides : scopeToLanguage === false ? { resource: overrides.resource } : overrides.overrideIdentifier && overriddenValue !== undefined ? overrides diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 47925bd1c08..91655eb0ff3 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -290,6 +290,9 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen initialValue: options?.content, encoding: options?.encoding }); + if (options?.encoding) { + await model.setEncoding(options.encoding); + } const resource = model.resource; const ref = await this._textModelResolverService.createModelReference(resource); if (!this._modelTrackers.has(resource)) { diff --git a/src/vs/workbench/api/browser/mainThreadMcp.ts b/src/vs/workbench/api/browser/mainThreadMcp.ts index 491cc5d2581..407a8d232b4 100644 --- a/src/vs/workbench/api/browser/mainThreadMcp.ts +++ b/src/vs/workbench/api/browser/mainThreadMcp.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from '../../../nls.js'; import { disposableTimeout } from '../../../base/common/async.js'; +import { CancellationError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; +import { IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata } from '../../../base/common/oauth.js'; import { ISettableObservable, observableValue } from '../../../base/common/observable.js'; import Severity from '../../../base/common/severity.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; +import * as nls from '../../../nls.js'; import { IDialogService, IPromptButton } from '../../../platform/dialogs/common/dialogs.js'; import { LogLevel } from '../../../platform/log/common/log.js'; import { IMcpMessageTransport, IMcpRegistry } from '../../contrib/mcp/common/mcpRegistryTypes.js'; @@ -23,8 +25,6 @@ import { ExtensionHostKind, extensionHostKindToString } from '../../services/ext import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { Proxied } from '../../services/extensions/common/proxyIdentifier.js'; import { ExtHostContext, ExtHostMcpShape, MainContext, MainThreadMcpShape } from '../common/extHost.protocol.js'; -import { CancellationError } from '../../../base/common/errors.js'; -import { IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata } from '../../../base/common/oauth.js'; @extHostNamedCustomer(MainContext.MainThreadMcp) export class MainThreadMcp extends Disposable implements MainThreadMcpShape { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a4e53cbd021..8a5d067160d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -68,6 +68,7 @@ import * as notebookCommon from '../../contrib/notebook/common/notebookCommon.js import { CellExecutionUpdateType } from '../../contrib/notebook/common/notebookExecutionService.js'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from '../../contrib/notebook/common/notebookExecutionStateService.js'; import { ICellRange } from '../../contrib/notebook/common/notebookRange.js'; +import { ISCMHistoryOptions } from '../../contrib/scm/common/history.js'; import { InputValidationType } from '../../contrib/scm/common/scm.js'; import { IWorkspaceSymbol, NotebookPriorityInfo } from '../../contrib/search/common/search.js'; import { IRawClosedNotebookFileMatch } from '../../contrib/search/common/searchNotebookHelpers.js'; @@ -203,7 +204,7 @@ export interface MainThreadSecretStateShape extends IDisposable { } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: unknown, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; } @@ -1409,7 +1410,7 @@ export interface ExtHostLanguageModelToolsShape { $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise | SerializableObjectWithBuffers>>; $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise; - $prepareToolInvocation(toolId: string, parameters: any, token: CancellationToken): Promise; + $prepareToolInvocation(toolId: string, parameters: unknown, token: CancellationToken): Promise; } export interface MainThreadUrlsShape extends IDisposable { @@ -2570,7 +2571,7 @@ export interface ExtHostSCMShape { $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; $provideHistoryItemRefs(sourceControlHandle: number, historyItemRefs: string[] | undefined, token: CancellationToken): Promise; - $provideHistoryItems(sourceControlHandle: number, options: any, token: CancellationToken): Promise; + $provideHistoryItems(sourceControlHandle: number, options: ISCMHistoryOptions, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 436e3505751..12d2a2bf41f 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -41,28 +41,21 @@ const newCommands: ApiCommand[] = [ if (isFalsyOrEmpty(value)) { return undefined; } - class MergedInfo extends types.SymbolInformation implements vscode.DocumentSymbol { - static to(symbol: languages.DocumentSymbol): MergedInfo { - const res = new MergedInfo( - symbol.name, - typeConverters.SymbolKind.to(symbol.kind), - symbol.containerName || '', - new types.Location(apiArgs[0], typeConverters.Range.to(symbol.range)) - ); - res.detail = symbol.detail; - res.range = res.location.range; - res.selectionRange = typeConverters.Range.to(symbol.selectionRange); - res.children = symbol.children ? symbol.children.map(MergedInfo.to) : []; - return res; - } - detail!: string; - range!: vscode.Range; - selectionRange!: vscode.Range; - children!: vscode.DocumentSymbol[]; - override containerName: string = ''; + function wrap(symbol: languages.DocumentSymbol): types.SymbolInformationAndDocumentSymbol { + return new types.SymbolInformationAndDocumentSymbol( + symbol.name, + typeConverters.SymbolKind.to(symbol.kind), + symbol.detail, + symbol.containerName || '', + apiArgs[0], + typeConverters.Range.to(symbol.range), + typeConverters.Range.to(symbol.selectionRange), + symbol.children ? symbol.children.map(wrap) : [] + ); } - return value.map(MergedInfo.to); + + return value.map(wrap); }) ), diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 5008199eb58..a9a03b472d2 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -16,7 +16,7 @@ import { URI, UriComponents } from '../../../base/common/uri.js'; import { fetchDynamicRegistration, getClaimsFromJWT, IAuthorizationJWTClaims, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, IAuthorizationTokenResponse, isAuthorizationTokenResponse } from '../../../base/common/oauth.js'; import { IExtHostWindow } from './extHostWindow.js'; import { IExtHostInitDataService } from './extHostInitDataService.js'; -import { ILogger, ILoggerService } from '../../../platform/log/common/log.js'; +import { ILogger, ILoggerService, ILogService } from '../../../platform/log/common/log.js'; import { autorun, derivedOpts, IObservable, ISettableObservable, observableValue } from '../../../base/common/observable.js'; import { stringHash } from '../../../base/common/hash.js'; import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; @@ -26,7 +26,7 @@ import { equals as arraysEqual } from '../../../base/common/arrays.js'; import { IExtHostProgress } from './extHostProgress.js'; import { IProgressStep } from '../../../platform/progress/common/progress.js'; import { CancellationError, isCancellationError } from '../../../base/common/errors.js'; -import { raceCancellationError } from '../../../base/common/async.js'; +import { raceCancellationError, SequencerByKey } from '../../../base/common/async.js'; export interface IExtHostAuthentication extends ExtHostAuthentication { } export const IExtHostAuthentication = createDecorator('IExtHostAuthentication'); @@ -46,6 +46,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _proxy: MainThreadAuthenticationShape; private _authenticationProviders: Map = new Map(); + private _providerOperations = new SequencerByKey(); private _onDidChangeSessions = new Emitter(); private _getSessionTaskSingler = new TaskSingler(); @@ -58,7 +59,8 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { @IExtHostWindow private readonly _extHostWindow: IExtHostWindow, @IExtHostUrlsService private readonly _extHostUrls: IExtHostUrlsService, @IExtHostProgress private readonly _extHostProgress: IExtHostProgress, - @ILoggerService private readonly _extHostLoggerService: ILoggerService + @ILoggerService private readonly _extHostLoggerService: ILoggerService, + @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadAuthentication); } @@ -98,58 +100,67 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { return await this._proxy.$getAccounts(providerId); } - async removeSession(providerId: string, sessionId: string): Promise { - const providerData = this._authenticationProviders.get(providerId); - if (!providerData) { - return this._proxy.$removeSession(providerId, sessionId); - } - - return providerData.provider.removeSession(sessionId); - } - registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable { - if (this._authenticationProviders.get(id)) { - throw new Error(`An authentication provider with id '${id}' is already registered.`); - } - - this._authenticationProviders.set(id, { label, provider, options: options ?? { supportsMultipleAccounts: false } }); - const listener = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(id, e)); - this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false, options?.supportedAuthorizationServers); + // register + void this._providerOperations.queue(id, async () => { + // This use to be synchronous, but that wasn't an accurate representation because the main thread + // may have unregistered the provider in the meantime. I don't see how this could really be done + // synchronously, so we just say first one wins. + if (this._authenticationProviders.get(id)) { + this._logService.error(`An authentication provider with id '${id}' is already registered. The existing provider will not be replaced.`); + return; + } + const listener = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(id, e)); + this._authenticationProviders.set(id, { label, provider, disposable: listener, options: options ?? { supportsMultipleAccounts: false } }); + await this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false, options?.supportedAuthorizationServers); + }); + // unregister return new Disposable(() => { - listener.dispose(); - this._authenticationProviders.delete(id); - this._proxy.$unregisterAuthenticationProvider(id); + void this._providerOperations.queue(id, async () => { + const providerData = this._authenticationProviders.get(id); + if (providerData) { + providerData.disposable?.dispose(); + this._authenticationProviders.delete(id); + await this._proxy.$unregisterAuthenticationProvider(id); + } + }); }); } - async $createSession(providerId: string, scopes: string[], options: vscode.AuthenticationProviderSessionOptions): Promise { - const providerData = this._authenticationProviders.get(providerId); - if (providerData) { - options.authorizationServer = URI.revive(options.authorizationServer); - return await providerData.provider.createSession(scopes, options); - } + $createSession(providerId: string, scopes: string[], options: vscode.AuthenticationProviderSessionOptions): Promise { + return this._providerOperations.queue(providerId, async () => { + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + options.authorizationServer = URI.revive(options.authorizationServer); + return await providerData.provider.createSession(scopes, options); + } - throw new Error(`Unable to find authentication provider with handle: ${providerId}`); + throw new Error(`Unable to find authentication provider with handle: ${providerId}`); + }); } - async $removeSession(providerId: string, sessionId: string): Promise { - const providerData = this._authenticationProviders.get(providerId); - if (providerData) { - return await providerData.provider.removeSession(sessionId); - } + $removeSession(providerId: string, sessionId: string): Promise { + return this._providerOperations.queue(providerId, async () => { + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + return await providerData.provider.removeSession(sessionId); + } - throw new Error(`Unable to find authentication provider with handle: ${providerId}`); + throw new Error(`Unable to find authentication provider with handle: ${providerId}`); + }); } - async $getSessions(providerId: string, scopes: ReadonlyArray | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise> { - const providerData = this._authenticationProviders.get(providerId); - if (providerData) { - options.authorizationServer = URI.revive(options.authorizationServer); - return await providerData.provider.getSessions(scopes, options); - } + $getSessions(providerId: string, scopes: ReadonlyArray | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise> { + return this._providerOperations.queue(providerId, async () => { + const providerData = this._authenticationProviders.get(providerId); + if (providerData) { + options.authorizationServer = URI.revive(options.authorizationServer); + return await providerData.provider.getSessions(scopes, options); + } - throw new Error(`Unable to find authentication provider with handle: ${providerId}`); + throw new Error(`Unable to find authentication provider with handle: ${providerId}`); + }); } $onDidChangeAuthenticationSessions(id: string, label: string, extensionIdFilter?: string[]) { @@ -160,18 +171,14 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { return Promise.resolve(); } - // Today, this only handles unregistering extensions that have disposables... - // so basiscally just the dynmaic ones. This was done to fix a bug where - // there was a racecondition between this event and re-registering a provider - // with the same id. (https://github.com/microsoft/vscode-copilot/issues/18045) - // This works for now, but should be cleaned up so theres one flow for register/unregister $onDidUnregisterAuthenticationProvider(id: string): Promise { - const providerData = this._authenticationProviders.get(id); - if (providerData?.disposable) { - providerData.disposable.dispose(); - this._authenticationProviders.delete(id); - } - return Promise.resolve(); + return this._providerOperations.queue(id, async () => { + const providerData = this._authenticationProviders.get(id); + if (providerData) { + providerData.disposable?.dispose(); + this._authenticationProviders.delete(id); + } + }); } async $registerDynamicAuthProvider( @@ -186,7 +193,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { throw new Error('Server does not support dynamic registration'); } try { - const registration = await fetchDynamicRegistration(serverMetadata.registration_endpoint, this._initData.environment.appName); + const registration = await fetchDynamicRegistration(serverMetadata.registration_endpoint, this._initData.environment.appName, resourceMetadata?.scopes_supported); clientId = registration.client_id; } catch (err) { throw new Error(`Dynamic registration failed: ${err.message}`); @@ -206,17 +213,22 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._onDidDynamicAuthProviderTokensChange, initialTokens || [] ); - const disposable = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(provider.id, e)); - this._authenticationProviders.set( - provider.id, - { - label: provider.label, - provider, - disposable: Disposable.from(provider, disposable), - options: { supportsMultipleAccounts: false } - } - ); - await this._proxy.$registerDynamicAuthenticationProvider(provider.id, provider.label, provider.authorizationServer, provider.clientId); + + // Use the sequencer to ensure dynamic provider registration is serialized + await this._providerOperations.queue(provider.id, async () => { + const disposable = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(provider.id, e)); + this._authenticationProviders.set( + provider.id, + { + label: provider.label, + provider, + disposable: Disposable.from(provider, disposable), + options: { supportsMultipleAccounts: false } + } + ); + await this._proxy.$registerDynamicAuthenticationProvider(provider.id, provider.label, provider.authorizationServer, provider.clientId); + }); + return provider.id; } @@ -272,12 +284,14 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider { initialTokens: IAuthorizationToken[], ) { const stringifiedServer = authorizationServer.toString(true); + // Auth Provider Id is a combination of the authorization server and the resource, if provided. this.id = _resourceMetadata?.resource ? stringifiedServer + ' ' + _resourceMetadata?.resource : stringifiedServer; + // Auth Provider label is just the resource name if provided, otherwise the authority of the authorization server. this.label = _resourceMetadata?.resource_name ?? this.authorizationServer.authority; - this._logger = loggerService.createLogger(stringifiedServer, { name: this.label }); + this._logger = loggerService.createLogger(this.id, { name: this.label }); this._disposable = new DisposableStore(); this._disposable.add(this._onDidChangeSessions); const scopedEvent = Event.chain(onDidDynamicAuthProviderTokensChange.event, $ => $ @@ -287,7 +301,7 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider { this._tokenStore = this._disposable.add(new TokenStore( { onDidChange: scopedEvent, - set: (tokens) => _proxy.$setSessionsForDynamicAuthProvider(stringifiedServer, this.clientId, tokens), + set: (tokens) => _proxy.$setSessionsForDynamicAuthProvider(this.id, this.clientId, tokens), }, initialTokens, this._logger diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index f0d9124a0da..425d303ff88 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -247,7 +247,7 @@ export class ExtHostConfigProvider { } return result; }, - update: (key: string, value: any, extHostConfigurationTarget: ExtHostConfigurationTarget | boolean, scopeToLanguage?: boolean) => { + update: (key: string, value: unknown, extHostConfigurationTarget: ExtHostConfigurationTarget | boolean, scopeToLanguage?: boolean) => { key = section ? `${section}.${key}` : key; const target = parseConfigurationTarget(extHostConfigurationTarget); if (value !== undefined) { @@ -299,7 +299,7 @@ export class ExtHostConfigProvider { set: (_target: any, property: PropertyKey, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${String(property)}' of object`); }, deleteProperty: (_target: any, property: PropertyKey) => { throw new Error(`TypeError: Cannot delete read only property '${String(property)}' of object`); }, defineProperty: (_target: any, property: PropertyKey) => { throw new Error(`TypeError: Cannot define property '${String(property)}' for a readonly object`); }, - setPrototypeOf: (_target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); }, + setPrototypeOf: (_target: unknown) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); }, isExtensible: () => false, preventExtensions: () => true }) : target; diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 4e7dd522c51..bbf8b4200c7 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -12,7 +12,7 @@ import { ExtHostDocumentData, setWordDefinitionFor } from './extHostDocumentData import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js'; import * as TypeConverters from './extHostTypeConverters.js'; import type * as vscode from 'vscode'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { deepFreeze } from '../../../base/common/objects.js'; import { TextDocumentChangeReason } from './extHostTypes.js'; @@ -88,7 +88,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { promise = this._proxy.$tryOpenDocument(uri, options).then(uriData => { this._documentLoader.delete(uri.toString()); const canonicalUri = URI.revive(uriData); - return assertIsDefined(this._documentsAndEditors.getDocument(canonicalUri)); + return assertReturnsDefined(this._documentsAndEditors.getDocument(canonicalUri)); }, err => { this._documentLoader.delete(uri.toString()); return Promise.reject(err); diff --git a/src/vs/workbench/api/common/extHostEditorTabs.ts b/src/vs/workbench/api/common/extHostEditorTabs.ts index 592c75c4bae..cabc7c37ac3 100644 --- a/src/vs/workbench/api/common/extHostEditorTabs.ts +++ b/src/vs/workbench/api/common/extHostEditorTabs.ts @@ -5,7 +5,7 @@ import { diffSets } from '../../../base/common/collections.js'; import { Emitter } from '../../../base/common/event.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { IEditorTabDto, IEditorTabGroupDto, IExtHostEditorTabsShape, MainContext, MainThreadEditorTabsShape, TabInputKind, TabModelOperationKind, TabOperation } from './extHost.protocol.js'; @@ -248,7 +248,7 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs { }, get activeTabGroup() { const activeTabGroupId = that._activeGroupId; - const activeTabGroup = assertIsDefined(that._extHostTabGroups.find(candidate => candidate.groupId === activeTabGroupId)?.apiObject); + const activeTabGroup = assertReturnsDefined(that._extHostTabGroups.find(candidate => candidate.groupId === activeTabGroupId)?.apiObject); return activeTabGroup; }, close: async (tabOrTabGroup: vscode.Tab | readonly vscode.Tab[] | vscode.TabGroup | readonly vscode.TabGroup[], preserveFocus?: boolean) => { @@ -300,7 +300,7 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs { }); // Set the active tab group id - const activeTabGroupId = assertIsDefined(tabGroups.find(group => group.isActive === true)?.groupId); + const activeTabGroupId = assertReturnsDefined(tabGroups.find(group => group.isActive === true)?.groupId); if (activeTabGroupId !== undefined && this._activeGroupId !== activeTabGroupId) { this._activeGroupId = activeTabGroupId; } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index dbaf5bbb595..a439fdc055a 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -593,8 +593,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme try { activationTimesBuilder.activateCallStart(); logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); - const scope = typeof global === 'object' ? global : self; // `global` is nodejs while `self` is for workers - const activateResult: Promise = extensionModule.activate.apply(scope, [context]); + const activateResult: Promise = extensionModule.activate.apply(globalThis, [context]); activationTimesBuilder.activateCallStop(); activationTimesBuilder.activateResolveStart(); diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 3fbd84d3392..5fcd3c613d0 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -14,7 +14,7 @@ import { Disposable, WorkspaceEdit } from './extHostTypes.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { FileChangeFilter, FileOperation, IGlobPatterns } from '../../../platform/files/common/files.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; -import { ILogService, LogLevel } from '../../../platform/log/common/log.js'; +import { ILogService } from '../../../platform/log/common/log.js'; import { IExtHostWorkspace } from './extHostWorkspace.js'; import { Lazy } from '../../../base/common/lazy.js'; import { ExtHostConfigProvider } from './extHostConfiguration.js'; @@ -29,9 +29,6 @@ export interface FileSystemWatcherCreateOptions { class FileSystemWatcher implements vscode.FileSystemWatcher { - private static IDS = 0; - - private readonly id = FileSystemWatcher.IDS++; private readonly session = Math.random(); private readonly _onDidCreate = new Emitter(); @@ -53,16 +50,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { return Boolean(this._config & 0b100); } - constructor( - mainContext: IMainContext, - logService: ILogService, - configuration: ExtHostConfigProvider, - workspace: IExtHostWorkspace, - extension: IExtensionDescription, - dispatcher: Event, - globPattern: string | IRelativePatternDto, - options: FileSystemWatcherCreateOptions - ) { + constructor(mainContext: IMainContext, configuration: ExtHostConfigProvider, workspace: IExtHostWorkspace, extension: IExtensionDescription, dispatcher: Event, globPattern: string | IRelativePatternDto, options: FileSystemWatcherCreateOptions) { this._config = 0; if (options.ignoreCreateEvents) { this._config += 0b001; @@ -74,18 +62,6 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { this._config += 0b100; } - const trace = logService.getLevel() === LogLevel.Trace; - if (trace) { - let patternLogMsg: string; - if (typeof globPattern === 'string') { - patternLogMsg = `'${globPattern}'`; - } else { - patternLogMsg = `base: '${globPattern.base}', pattern: '${globPattern.pattern}'`; - } - - logService.trace(`[File Watcher ('API') ${this.id}] createFileSystemWatcher(${patternLogMsg}, ${JSON.stringify(options)})`); - } - const parsedPattern = parse(globPattern); // 1.64.x behaviour change: given the new support to watch any folder @@ -105,64 +81,34 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { const subscription = dispatcher(events => { if (typeof events.session === 'number' && events.session !== this.session) { - if (trace) { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(): returning early due to event correlation mismatch`); - } return; // ignore events from other file watchers that are in correlation mode } if (excludeUncorrelatedEvents && typeof events.session === 'undefined') { - if (trace) { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(): returning early due to event correlation mismatch`); - } return; // ignore events from other non-correlating file watcher when we are in correlation mode } if (!options.ignoreCreateEvents) { for (const created of events.created) { const uri = URI.revive(created); - if (parsedPattern(uri.fsPath)) { - if (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri)) { - this._onDidCreate.fire(uri); - } else { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(created): ${uri.fsPath} did not match out-of-workspace rule`); - } - } else { - if (trace) { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(created): ${uri.fsPath} did not match pattern`); - } + if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) { + this._onDidCreate.fire(uri); } } } if (!options.ignoreChangeEvents) { for (const changed of events.changed) { const uri = URI.revive(changed); - if (parsedPattern(uri.fsPath)) { - if (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri)) { - this._onDidChange.fire(uri); - } else { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(changed): ${uri.fsPath} did not match out-of-workspace rule`); - } - } else { - if (trace) { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(changed): ${uri.fsPath} did not match pattern`); - } + if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) { + this._onDidChange.fire(uri); } } } if (!options.ignoreDeleteEvents) { for (const deleted of events.deleted) { const uri = URI.revive(deleted); - if (parsedPattern(uri.fsPath)) { - if (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri)) { - this._onDidDelete.fire(uri); - } else { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(deleted): ${uri.fsPath} did not match out-of-workspace rule`); - } - } else { - if (trace) { - logService.trace(`[File Watcher ('API') ${this.id}] dispatch(deleted): ${uri.fsPath} did not match pattern`); - } + if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) { + this._onDidDelete.fire(uri); } } } @@ -339,7 +285,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ //--- file events createFileSystemWatcher(workspace: IExtHostWorkspace, configProvider: ExtHostConfigProvider, extension: IExtensionDescription, globPattern: vscode.GlobPattern, options: FileSystemWatcherCreateOptions): vscode.FileSystemWatcher { - return new FileSystemWatcher(this._mainContext, this._logService, configProvider, workspace, extension, this._onFileSystemEvent.event, typeConverter.GlobPattern.from(globPattern), options); + return new FileSystemWatcher(this._mainContext, configProvider, workspace, extension, this._onFileSystemEvent.event, typeConverter.GlobPattern.from(globPattern), options); } $onFileEvent(events: FileSystemEvents) { diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index bc1b1a263a2..f90a6a5d6e2 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -220,7 +220,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape return model; } - async $prepareToolInvocation(toolId: string, input: any, token: CancellationToken): Promise { + async $prepareToolInvocation(toolId: string, input: unknown, token: CancellationToken): Promise { const item = this._registeredTools.get(toolId); if (!item) { throw new Error(`Unknown tool ${toolId}`); diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 2ab508631e7..f2fcab92ffc 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -263,7 +263,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { sendSoon({ index: fragment.index, part }); }); - let value: any; + let value: unknown; try { value = data.provider.provideLanguageModelResponse( diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index db96d1e2124..4f8b7e44edd 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -10,7 +10,7 @@ import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } import { SSEParser } from '../../../base/common/sseParser.js'; import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; -import { LogLevel } from '../../../platform/log/common/log.js'; +import { canLog, ILogService, LogLevel } from '../../../platform/log/common/log.js'; import { StorageScope } from '../../../platform/storage/common/storage.js'; import { extensionPrefixedIdentifier, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch, McpServerTransportHTTP, McpServerTransportType } from '../../contrib/mcp/common/mcpTypes.js'; import { ExtHostMcpShape, MainContext, MainThreadMcpShape } from './extHost.protocol.js'; @@ -40,6 +40,7 @@ export class ExtHostMcpService extends Disposable implements IExtHostMpcService constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService private readonly _logService: ILogService, @IExtHostInitDataService private readonly _extHostInitData: IExtHostInitDataService ) { super(); @@ -52,7 +53,7 @@ export class ExtHostMcpService extends Disposable implements IExtHostMpcService protected _startMcp(id: number, launch: McpServerLaunch): void { if (launch.type === McpServerTransportType.HTTP) { - this._sseEventSources.set(id, new McpHTTPHandle(id, launch, this._proxy)); + this._sseEventSources.set(id, new McpHTTPHandle(id, launch, this._proxy, this._logService)); return; } @@ -197,7 +198,8 @@ class McpHTTPHandle extends Disposable { constructor( private readonly _id: number, private readonly _launch: McpServerTransportHTTP, - private readonly _proxy: MainThreadMcpShape + private readonly _proxy: MainThreadMcpShape, + private readonly _logService: ILogService, ) { super(); @@ -246,7 +248,6 @@ class McpHTTPHandle extends Disposable { this._launch.uri.toString(true), { method: 'POST', - signal: this._abortCtrl.signal, headers, body: asBytes, }, @@ -280,7 +281,7 @@ class McpHTTPHandle extends Disposable { this._proxy.$onDidChangeState(this._id, { state: McpConnectionState.Kind.Error, - message: `${res.status} status sending message to ${this._launch.uri}: ${await this._getErrText(res)}` + retryWithSessionId ? `; will retry with new session ID` : '', + message: `${res.status} status sending message to ${this._launch.uri}: ${await this._getErrText(res)}` + (retryWithSessionId ? `; will retry with new session ID` : ''), shouldRetry: retryWithSessionId, }); return; @@ -374,9 +375,8 @@ class McpHTTPHandle extends Disposable { ...Object.fromEntries(this._launch.headers) }; } - const resourceMetadataResponse = await fetch(resourceMetadata, { + const resourceMetadataResponse = await this._fetch(resourceMetadata, { method: 'GET', - signal: this._abortCtrl.signal, headers: { ...additionalHeaders, 'Accept': 'application/json', @@ -401,9 +401,8 @@ class McpHTTPHandle extends Disposable { const authorizationServerUrl = new URL(authorizationServer); const extraPath = authorizationServerUrl.pathname === '/' ? '' : authorizationServerUrl.pathname; const pathToFetch = new URL(AUTH_SERVER_METADATA_DISCOVERY_PATH, authorizationServer).toString() + extraPath; - let authServerMetadataResponse = await fetch(pathToFetch, { + let authServerMetadataResponse = await this._fetch(pathToFetch, { method: 'GET', - signal: this._abortCtrl.signal, headers: { ...addtionalHeaders, 'Accept': 'application/json', @@ -414,11 +413,10 @@ class McpHTTPHandle extends Disposable { // Try fetching the other discovery URL. For the openid metadata discovery // path, we _ADD_ the well known path after the existing path. // https://datatracker.ietf.org/doc/html/rfc8414#section-3 - authServerMetadataResponse = await fetch( + authServerMetadataResponse = await this._fetch( URI.joinPath(URI.parse(authorizationServer), '.well-known', 'openid-configuration').toString(true), { method: 'GET', - signal: this._abortCtrl.signal, headers: { ...addtionalHeaders, 'Accept': 'application/json', @@ -505,7 +503,6 @@ class McpHTTPHandle extends Disposable { this._launch.uri.toString(true), { method: 'GET', - signal: this._abortCtrl.signal, headers, }, headers @@ -520,7 +517,11 @@ class McpHTTPHandle extends Disposable { return; } - retry = 0; + // Only reset the retry counter if we definitely get an event stream to avoid + // spamming servers that (incorrectly) don't return one from this endpoint. + if (res.headers.get('content-type')?.toLowerCase().includes('text/event-stream')) { + retry = 0; + } const parser = new SSEParser(event => { if (event.type === 'message') { @@ -557,7 +558,6 @@ class McpHTTPHandle extends Disposable { this._launch.uri.toString(true), { method: 'GET', - signal: this._abortCtrl.signal, headers, }, headers @@ -599,9 +599,8 @@ class McpHTTPHandle extends Disposable { 'Content-Length': String(asBytes.length), }; await this._addAuthHeader(headers); - const res = await fetch(url, { + const res = await this._fetch(url, { method: 'POST', - signal: this._abortCtrl.signal, headers, body: asBytes, }); @@ -670,8 +669,8 @@ class McpHTTPHandle extends Disposable { * If the initial request returns 401 and we don't have auth metadata, * it will populate the auth metadata and retry once. */ - private async _fetchWithAuthRetry(url: string, init: RequestInit, headers: Record): Promise { - const doFetch = () => fetch(url, init); + private async _fetchWithAuthRetry(url: string, init: MinimalRequestInit, headers: Record): Promise { + const doFetch = () => this._fetch(url, init); let res = await doFetch(); if (res.status === 401) { @@ -687,6 +686,40 @@ class McpHTTPHandle extends Disposable { } return res; } + + private async _fetch(url: string, init: MinimalRequestInit): Promise { + if (canLog(this._logService.getLevel(), LogLevel.Trace)) { + const traceObj: any = { ...init, headers: { ...init.headers } }; + if (traceObj.body) { + traceObj.body = new TextDecoder().decode(traceObj.body); + } + if (traceObj.headers?.Authorization) { + traceObj.headers.Authorization = '***'; // don't log the auth header + } + this._log(LogLevel.Trace, `Fetching ${url} with options: ${JSON.stringify(traceObj)}`); + } + const res = await fetch(url, { + ...init, + signal: this._abortCtrl.signal, + }); + + if (canLog(this._logService.getLevel(), LogLevel.Trace)) { + const headers: Record = {}; + res.headers.forEach((value, key) => { headers[key] = value; }); + this._log(LogLevel.Trace, `Fetched ${url}: ${JSON.stringify({ + status: res.status, + headers: headers, + })}`); + } + + return res; + } +} + +interface MinimalRequestInit { + method: string; + headers: Record; + body?: Uint8Array; } function isJSON(str: string): boolean { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 75b64857223..1dea8c800d7 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -12,7 +12,7 @@ import { DisposableStore, IDisposable, toDisposable } from '../../../base/common import { ResourceMap, ResourceSet } from '../../../base/common/map.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { isFalsyOrWhitespace } from '../../../base/common/strings.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import * as files from '../../../platform/files/common/files.js'; @@ -200,7 +200,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } const canonicalUri = await this._notebookDocumentsProxy.$tryOpenNotebook(uri); const document = this._documents.get(URI.revive(canonicalUri)); - return assertIsDefined(document?.apiNotebook); + return assertReturnsDefined(document?.apiNotebook); } async showNotebookDocument(notebook: vscode.NotebookDocument, options?: vscode.NotebookDocumentShowOptions): Promise { @@ -327,7 +327,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } if (document.versionId !== versionId) { - throw new Error('Document version mismatch'); + throw new Error('Document version mismatch, expected: ' + versionId + ', actual: ' + document.versionId); } if (!this._extHostFileSystem.value.isWritableFileSystem(uri.scheme)) { diff --git a/src/vs/workbench/api/common/extHostNotebookEditor.ts b/src/vs/workbench/api/common/extHostNotebookEditor.ts index 7ef4dc5f5ae..f864395dbe6 100644 --- a/src/vs/workbench/api/common/extHostNotebookEditor.ts +++ b/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -94,7 +94,7 @@ export class ExtHostNotebookEditor { } _acceptSelections(selections: vscode.NotebookRange[]): void { - this._selections = selections.length === 0 ? [new NotebookRange(0, 0)] : selections; + this._selections = selections; } private _trySetSelections(value: vscode.NotebookRange[]): void { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index d96cbc78588..6e11a2c8d6b 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -31,7 +31,7 @@ interface IKernelData { extensionId: ExtensionIdentifier; controller: vscode.NotebookController; onDidChangeSelection: Emitter<{ selected: boolean; notebook: vscode.NotebookDocument }>; - onDidReceiveMessage: Emitter<{ editor: vscode.NotebookEditor; message: any }>; + onDidReceiveMessage: Emitter<{ editor: vscode.NotebookEditor; message: unknown }>; associatedNotebooks: ResourceMap; } @@ -135,7 +135,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { let isDisposed = false; const onDidChangeSelection = new Emitter<{ selected: boolean; notebook: vscode.NotebookDocument }>(); - const onDidReceiveMessage = new Emitter<{ editor: vscode.NotebookEditor; message: any }>(); + const onDidReceiveMessage = new Emitter<{ editor: vscode.NotebookEditor; message: unknown }>(); const data: INotebookKernelDto2 = { id: createKernelId(extension.identifier, id), diff --git a/src/vs/workbench/api/common/extHostNotebookRenderers.ts b/src/vs/workbench/api/common/extHostNotebookRenderers.ts index 56c273a7b0d..27035ab4590 100644 --- a/src/vs/workbench/api/common/extHostNotebookRenderers.ts +++ b/src/vs/workbench/api/common/extHostNotebookRenderers.ts @@ -12,7 +12,7 @@ import * as vscode from 'vscode'; export class ExtHostNotebookRenderers implements ExtHostNotebookRenderersShape { - private readonly _rendererMessageEmitters = new Map>(); + private readonly _rendererMessageEmitters = new Map>(); private readonly proxy: MainThreadNotebookRenderersShape; constructor(mainContext: IMainContext, private readonly _extHostNotebook: ExtHostNotebookController) { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index eb77029e5f4..236a32470f5 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -1085,7 +1085,7 @@ export class ExtHostSCM implements ExtHostSCMShape { } } - async $provideHistoryItems(sourceControlHandle: number, options: any, token: CancellationToken): Promise { + async $provideHistoryItems(sourceControlHandle: number, options: vscode.SourceControlHistoryOptions, token: CancellationToken): Promise { try { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; const historyItems = await historyProvider?.provideHistoryItems(options, token); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 693cb1728a4..7f218f1e162 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -18,7 +18,7 @@ import { serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollect import { CancellationTokenSource } from '../../../base/common/cancellation.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { IEnvironmentVariableCollectionDescription, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from '../../../platform/terminal/common/environmentVariable.js'; -import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, TerminalShellType } from '../../../platform/terminal/common/terminal.js'; +import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, TerminalShellType, WindowsShellType } from '../../../platform/terminal/common/terminal.js'; import { TerminalDataBufferer } from '../../../platform/terminal/common/terminalDataBuffering.js'; import { ThemeColor } from '../../../base/common/themables.js'; import { Promises } from '../../../base/common/async.js'; @@ -27,6 +27,7 @@ import { TerminalCompletionList, TerminalQuickFix, ViewColumn } from './extHostT import { IExtHostCommands } from './extHostCommands.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { ISerializedTerminalInstanceContext } from '../../contrib/terminal/common/terminal.js'; +import { isWindows } from '../../../base/common/platform.js'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -334,6 +335,11 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { this._pty.handleInput?.(data); } + sendSignal(signal: string): void { + // Extension owned terminals don't support sending signals directly to processes + // This could be extended in the future if the pseudoterminal API is enhanced + } + resize(cols: number, rows: number): void { this._pty.setDimensions?.({ columns: cols, rows }); } @@ -776,7 +782,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I if (completions === null || completions === undefined) { return undefined; } - return TerminalCompletionList.from(completions); + const pathSeparator = !isWindows || this.activeTerminal.state?.shell === WindowsShellType.GitBash ? '/' : '\\'; + return TerminalCompletionList.from(completions, pathSeparator); } public $acceptTerminalShellType(id: number, shellType: TerminalShellType | undefined): void { diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 86a322348a8..b523bf14a82 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -456,6 +456,9 @@ export class ExtHostTextEditor { if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) { throw illegalArgument('selections'); } + if (value.length === 0) { + value = [new Selection(0, 0, 0, 0)]; + } that._selections = value; that._trySetSelection(); }, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index be99e78a443..e4a7378a3ae 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -16,7 +16,6 @@ import { parse, revive } from '../../../base/common/marshalling.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { Mimes } from '../../../base/common/mime.js'; import { cloneAndChange } from '../../../base/common/objects.js'; -import { isWindows } from '../../../base/common/platform.js'; import { IPrefixTreeNode, WellDefinedPrefixTree } from '../../../base/common/prefixTree.js'; import { basename } from '../../../base/common/resources.js'; import { ThemeIcon } from '../../../base/common/themables.js'; @@ -41,7 +40,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; -import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatModel.js'; +import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatPrepareToolInvocationPart, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import * as chatProvider from '../../contrib/chat/common/languageModels.js'; @@ -407,7 +406,7 @@ export namespace MarkdownString { if (!part) { return part; } - let data: any; + let data: unknown; try { data = parse(part); } catch (e) { @@ -3188,7 +3187,7 @@ export namespace TerminalCompletionItemDto { } export namespace TerminalCompletionList { - export function from(completions: vscode.TerminalCompletionList | vscode.TerminalCompletionItem[]): extHostProtocol.TerminalCompletionListDto { + export function from(completions: vscode.TerminalCompletionList | vscode.TerminalCompletionItem[], pathSeparator: string): extHostProtocol.TerminalCompletionListDto { if (Array.isArray(completions)) { return { items: completions.map(i => TerminalCompletionItemDto.from(i)), @@ -3196,17 +3195,17 @@ export namespace TerminalCompletionList { } return { items: completions.items.map(i => TerminalCompletionItemDto.from(i)), - resourceRequestConfig: completions.resourceRequestConfig ? TerminalResourceRequestConfig.from(completions.resourceRequestConfig) : undefined, + resourceRequestConfig: completions.resourceRequestConfig ? TerminalResourceRequestConfig.from(completions.resourceRequestConfig, pathSeparator) : undefined, }; } } export namespace TerminalResourceRequestConfig { - export function from(resourceRequestConfig: vscode.TerminalResourceRequestConfig): extHostProtocol.TerminalResourceRequestConfigDto { + export function from(resourceRequestConfig: vscode.TerminalResourceRequestConfig, pathSeparator: string): extHostProtocol.TerminalResourceRequestConfigDto { return { ...resourceRequestConfig, - pathSeparator: isWindows ? '\\' : '/', - cwd: resourceRequestConfig.cwd ? URI.revive(resourceRequestConfig.cwd) : undefined, + pathSeparator, + cwd: resourceRequestConfig.cwd, }; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d729adf4034..f74586216d4 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -584,9 +584,9 @@ export class RemoteAuthorityResolverError extends Error { public readonly _message: string | undefined; public readonly _code: RemoteAuthorityResolverErrorCode; - public readonly _detail: any; + public readonly _detail: unknown; - constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: any) { + constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: unknown) { super(message); this._message = message; @@ -1383,19 +1383,8 @@ export class SymbolInformation { } } -@es5ClassCompat -export class DocumentSymbol { - - static validate(candidate: DocumentSymbol): void { - if (!candidate.name) { - throw new Error('name must not be falsy'); - } - if (!candidate.range.contains(candidate.selectionRange)) { - throw new Error('selectionRange must be contained in fullRange'); - } - candidate.children?.forEach(DocumentSymbol.validate); - } +abstract class AbstractDocumentSymbol { name: string; detail: string; kind: SymbolKind; @@ -1411,11 +1400,56 @@ export class DocumentSymbol { this.range = range; this.selectionRange = selectionRange; this.children = []; - - DocumentSymbol.validate(this); } } +@es5ClassCompat +export class DocumentSymbol extends AbstractDocumentSymbol { + + static validate(candidate: DocumentSymbol): void { + if (!candidate.name) { + throw new Error('name must not be falsy'); + } + if (!candidate.range.contains(candidate.selectionRange)) { + throw new Error('selectionRange must be contained in fullRange'); + } + candidate.children?.forEach(DocumentSymbol.validate); + } + + constructor(name: string, detail: string, kind: SymbolKind, range: Range, selectionRange: Range) { + super(name, detail, kind, range, selectionRange); + DocumentSymbol.validate(this); + } + + static override[Symbol.hasInstance](candidate: unknown): boolean { + if (!isObject(candidate)) { + throw new TypeError(); + } + return candidate instanceof AbstractDocumentSymbol + || candidate instanceof SymbolInformationAndDocumentSymbol; + } +} + +// This is a special type that's used from the `vscode.executeDocumentSymbolProvider` API +// command which implements both shapes, vscode.SymbolInformation _and_ vscode.DocumentSymbol +export class SymbolInformationAndDocumentSymbol extends SymbolInformation implements vscode.DocumentSymbol { + + detail: string; + range: vscode.Range; + selectionRange: vscode.Range; + children: vscode.DocumentSymbol[]; + override containerName: string; + + constructor(name: string, kind: vscode.SymbolKind, detail: string, containerName: string, uri: URI, range: Range, selectionRange: Range, children?: SymbolInformationAndDocumentSymbol[]) { + super(name, kind, containerName, new Location(uri, range)); + + this.containerName = containerName; + this.detail = detail; + this.range = range; + this.selectionRange = selectionRange; + this.children = children ?? []; + } +} export enum CodeActionTriggerKind { Invoke = 1, @@ -2154,6 +2188,8 @@ export enum TerminalCompletionItemKind { Option = 5, OptionValue = 6, Flag = 7, + SymbolicLinkFile = 8, + SymbolicLinkFolder = 9 } export class TerminalCompletionItem implements vscode.TerminalCompletionItem { diff --git a/src/vs/workbench/api/common/extHostWebviewMessaging.ts b/src/vs/workbench/api/common/extHostWebviewMessaging.ts index 49f0fa06c99..97a9de1b30c 100644 --- a/src/vs/workbench/api/common/extHostWebviewMessaging.ts +++ b/src/vs/workbench/api/common/extHostWebviewMessaging.ts @@ -20,7 +20,7 @@ class ArrayBufferSet { } export function serializeWebviewMessage( - message: any, + message: unknown, options: { serializeBuffersForPostMessage?: boolean } ): { message: string; buffers: VSBuffer[] } { if (options.serializeBuffersForPostMessage) { @@ -83,7 +83,7 @@ function getTypedArrayType(value: ArrayBufferView): extHostProtocol.WebviewMessa return undefined; } -export function deserializeWebviewMessage(jsonMessage: string, buffers: VSBuffer[]): { message: any; arrayBuffers: ArrayBuffer[] } { +export function deserializeWebviewMessage(jsonMessage: string, buffers: VSBuffer[]): { message: unknown; arrayBuffers: ArrayBuffer[] } { const arrayBuffers: ArrayBuffer[] = buffers.map(buffer => { const arrayBuffer = new ArrayBuffer(buffer.byteLength); const uint8Array = new Uint8Array(arrayBuffer); @@ -117,6 +117,6 @@ export function deserializeWebviewMessage(jsonMessage: string, buffers: VSBuffer return value; }; - const message = JSON.parse(jsonMessage, reviver); + const message = JSON.parse(jsonMessage, reviver) as unknown; return { message, arrayBuffers }; } diff --git a/src/vs/workbench/api/node/extHostAuthentication.ts b/src/vs/workbench/api/node/extHostAuthentication.ts index 465e757d610..bffde4883b4 100644 --- a/src/vs/workbench/api/node/extHostAuthentication.ts +++ b/src/vs/workbench/api/node/extHostAuthentication.ts @@ -5,192 +5,22 @@ import * as nls from '../../../nls.js'; import type * as vscode from 'vscode'; -import * as http from 'http'; -import { randomBytes } from 'crypto'; import { URL } from 'url'; import { ExtHostAuthentication, DynamicAuthProvider, IExtHostAuthentication } from '../common/extHostAuthentication.js'; import { IExtHostRpcService } from '../common/extHostRpcService.js'; import { IExtHostInitDataService } from '../common/extHostInitDataService.js'; import { IExtHostWindow } from '../common/extHostWindow.js'; import { IExtHostUrlsService } from '../common/extHostUrls.js'; -import { ILogger, ILoggerService } from '../../../platform/log/common/log.js'; +import { ILoggerService, ILogService } from '../../../platform/log/common/log.js'; import { MainThreadAuthenticationShape } from '../common/extHost.protocol.js'; -import { IAuthorizationServerMetadata, IAuthorizationProtectedResourceMetadata, IAuthorizationTokenResponse, DEFAULT_AUTH_FLOW_PORT, IAuthorizationDeviceResponse, isAuthorizationDeviceResponse, isAuthorizationTokenResponse, IAuthorizationDeviceTokenErrorResponse } from '../../../base/common/oauth.js'; +import { IAuthorizationServerMetadata, IAuthorizationProtectedResourceMetadata, IAuthorizationTokenResponse, IAuthorizationDeviceResponse, isAuthorizationDeviceResponse, isAuthorizationTokenResponse, IAuthorizationDeviceTokenErrorResponse } from '../../../base/common/oauth.js'; import { Emitter } from '../../../base/common/event.js'; -import { DeferredPromise, raceCancellationError } from '../../../base/common/async.js'; +import { raceCancellationError } from '../../../base/common/async.js'; import { IExtHostProgress } from '../common/extHostProgress.js'; import { IProgressStep } from '../../../platform/progress/common/progress.js'; import { CancellationError, isCancellationError } from '../../../base/common/errors.js'; import { URI } from '../../../base/common/uri.js'; - -interface IOAuthResult { - code: string; - state: string; -} - -interface ILoopbackServer { - /** - * The state parameter used in the OAuth flow. - */ - readonly state: string; - - /** - * Starts the server. - * @throws If the server fails to start. - * @throws If the server is already started. - */ - start(): Promise; - - /** - * Stops the server. - * @throws If the server is not started. - * @throws If the server fails to stop. - */ - stop(): Promise; - - /** - * Returns a promise that resolves to the result of the OAuth flow. - */ - waitForOAuthResponse(): Promise; -} - -class LoopbackAuthServer implements ILoopbackServer { - private readonly _server: http.Server; - private readonly _resultPromise: Promise; - - private _state = randomBytes(16).toString('base64'); - private _port: number | undefined; - - constructor(private readonly _logger: ILogger, private readonly _appUri: URI) { - const deferredPromise = new DeferredPromise(); - this._resultPromise = deferredPromise.p; - - this._server = http.createServer((req, res) => { - const reqUrl = new URL(req.url!, `http://${req.headers.host}`); - switch (reqUrl.pathname) { - case '/': { - const code = reqUrl.searchParams.get('code') ?? undefined; - const state = reqUrl.searchParams.get('state') ?? undefined; - const error = reqUrl.searchParams.get('error') ?? undefined; - if (error) { - res.writeHead(302, { location: `/?error=${reqUrl.searchParams.get('error_description')}` }); - res.end(); - deferredPromise.error(new Error(error)); - break; - } - if (!code || !state) { - res.writeHead(400); - res.end(); - break; - } - if (this.state !== state) { - res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}` }); - res.end(); - deferredPromise.error(new Error('State does not match.')); - break; - } - deferredPromise.complete({ code, state }); - res.writeHead(302, { location: '/success' }); - res.end(); - break; - } - // Serve the static files - case '/success': - this._sendSuccessPage(res); - break; - default: - res.writeHead(404); - res.end(); - break; - } - }); - } - - get state(): string { return this._state; } - get redirectUri(): string { - if (this._port === undefined) { - throw new Error('Server is not started yet'); - } - return `http://127.0.0.1:${this._port}/`; - } - - private _sendSuccessPage(res: http.ServerResponse): void { - const html = getHtml(this._appUri); - res.writeHead(200, { - 'Content-Type': 'text/html', - 'Content-Length': Buffer.byteLength(html, 'utf8') - }); - res.end(html); - } - - start(): Promise { - const deferredPromise = new DeferredPromise(); - if (this._server.listening) { - throw new Error('Server is already started'); - } - const portTimeout = setTimeout(() => { - deferredPromise.error(new Error('Timeout waiting for port')); - }, 5000); - this._server.on('listening', () => { - const address = this._server.address(); - if (typeof address === 'string') { - this._port = parseInt(address); - } else if (address instanceof Object) { - this._port = address.port; - } else { - throw new Error('Unable to determine port'); - } - - clearTimeout(portTimeout); - deferredPromise.complete(); - }); - this._server.on('error', err => { - if ('code' in err && err.code === 'EADDRINUSE') { - this._logger.error('Address in use, retrying with a different port...'); - setTimeout(() => { - this._server.close(); - // Best effort to use a specific port, but fallback to a random one if it is in use - this._server.listen(0, '127.0.0.1'); - }, 1000); - return; - } - clearTimeout(portTimeout); - deferredPromise.error(new Error(`Error listening to server: ${err}`)); - }); - this._server.on('close', () => { - deferredPromise.error(new Error('Closed')); - }); - // Best effort to use a specific port, but fallback to a random one if it is in use - this._server.listen(DEFAULT_AUTH_FLOW_PORT, '127.0.0.1'); - return deferredPromise.p; - } - - stop(): Promise { - const deferredPromise = new DeferredPromise(); - if (!this._server.listening) { - deferredPromise.complete(); - return deferredPromise.p; - } - this._server.close((err) => { - if (err) { - deferredPromise.error(err); - } else { - deferredPromise.complete(); - } - }); - // If the server is not closed within 5 seconds, reject the promise - setTimeout(() => { - if (!deferredPromise.isResolved) { - deferredPromise.error(new Error('Timeout waiting for server to close')); - } - }, 5000); - return deferredPromise.p; - } - - waitForOAuthResponse(): Promise { - return this._resultPromise; - } -} +import { LoopbackAuthServer } from './loopbackServer.js'; export class NodeDynamicAuthProvider extends DynamicAuthProvider { @@ -232,11 +62,13 @@ export class NodeDynamicAuthProvider extends DynamicAuthProvider { }); } - // Add device code flow support (works in all environments) - this._createFlows.push({ - label: nls.localize('device code', "Device Code"), - handler: (scopes, progress, token) => this._createWithDeviceCode(scopes, progress, token) - }); + // Add device code flow to the end since it's not as streamlined + if (serverMetadata.device_authorization_endpoint) { + this._createFlows.push({ + label: nls.localize('device code', "Device Code"), + handler: (scopes, progress, token) => this._createWithDeviceCode(scopes, progress, token) + }); + } } private async _createWithLoopbackServer(scopes: string[], progress: vscode.Progress, token: vscode.CancellationToken): Promise { @@ -270,7 +102,11 @@ export class NodeDynamicAuthProvider extends DynamicAuthProvider { } // Create and start the loopback server - const server = new LoopbackAuthServer(this._logger, appUri); + const server = new LoopbackAuthServer( + this._logger, + appUri, + this._initData.environment.appName + ); try { await server.start(); } catch (err) { @@ -477,146 +313,8 @@ export class NodeExtHostAuthentication extends ExtHostAuthentication implements extHostUrls: IExtHostUrlsService, extHostProgress: IExtHostProgress, extHostLoggerService: ILoggerService, + extHostLogService: ILogService ) { - super(extHostRpc, initData, extHostWindow, extHostUrls, extHostProgress, extHostLoggerService); + super(extHostRpc, initData, extHostWindow, extHostUrls, extHostProgress, extHostLoggerService, extHostLogService); } } - -function getHtml(appUri: URI) { - return ` - - - - - GitHub Authentication - Sign In - - - - - - - Visual Studio Code - -
-
- You are signed in now and can close this page. -
-
- An error occurred while signing in: -
-
-
- - -`; -} diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index edc1452c76d..92cf4652104 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -16,7 +16,7 @@ import { Schemas } from '../../../base/common/network.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { ExtensionRuntime } from '../common/extHostTypes.js'; import { CLIServer } from './extHostCLIServer.js'; -import { realpathSync } from '../../../base/node/extpath.js'; +import { realpathSync } from '../../../base/node/pfs.js'; import { ExtHostConsoleForwarder } from './extHostConsoleForwarder.js'; import { ExtHostDiskFileSystemProvider } from './extHostDiskFileSystemProvider.js'; import nodeModule from 'node:module'; diff --git a/src/vs/workbench/api/node/extHostMcpNode.ts b/src/vs/workbench/api/node/extHostMcpNode.ts index 49550059ceb..ee90057f2e3 100644 --- a/src/vs/workbench/api/node/extHostMcpNode.ts +++ b/src/vs/workbench/api/node/extHostMcpNode.ts @@ -8,27 +8,27 @@ import { readFile } from 'fs/promises'; import { homedir } from 'os'; import { parseEnvFile } from '../../../base/common/envfile.js'; import { untildify } from '../../../base/common/labels.js'; +import { DisposableMap } from '../../../base/common/lifecycle.js'; +import * as path from '../../../base/common/path.js'; import { StreamSplitter } from '../../../base/node/nodeStreams.js'; import { findExecutable } from '../../../base/node/processes.js'; -import { LogLevel } from '../../../platform/log/common/log.js'; +import { ILogService, LogLevel } from '../../../platform/log/common/log.js'; import { McpConnectionState, McpServerLaunch, McpServerTransportStdio, McpServerTransportType } from '../../contrib/mcp/common/mcpTypes.js'; +import { McpStdioStateHandler } from '../../contrib/mcp/node/mcpStdioStateHandler.js'; +import { IExtHostInitDataService } from '../common/extHostInitDataService.js'; import { ExtHostMcpService } from '../common/extHostMcp.js'; import { IExtHostRpcService } from '../common/extHostRpcService.js'; -import * as path from '../../../base/common/path.js'; -import { IExtHostInitDataService } from '../common/extHostInitDataService.js'; export class NodeExtHostMpcService extends ExtHostMcpService { constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initDataService: IExtHostInitDataService, + @ILogService logService: ILogService, ) { - super(extHostRpc, initDataService); + super(extHostRpc, logService, initDataService); } - private nodeServers = new Map(); + private nodeServers = this._register(new DisposableMap()); protected override _startMcp(id: number, launch: McpServerLaunch): void { if (launch.type === McpServerTransportType.Stdio) { @@ -41,8 +41,7 @@ export class NodeExtHostMpcService extends ExtHostMcpService { override $stopMcp(id: number): void { const nodeServer = this.nodeServers.get(id); if (nodeServer) { - nodeServer.abortCtrl.abort(); - this.nodeServers.delete(id); + nodeServer.stop(); // will get removed from map when process is fully stopped } else { super.$stopMcp(id); } @@ -51,7 +50,7 @@ export class NodeExtHostMpcService extends ExtHostMcpService { override $sendMessage(id: number, message: string): void { const nodeServer = this.nodeServers.get(id); if (nodeServer) { - nodeServer.child.stdin.write(message + '\n'); + nodeServer.write(message); } else { super.$sendMessage(id, message); } @@ -81,7 +80,6 @@ export class NodeExtHostMpcService extends ExtHostMcpService { env[key] = value === null ? undefined : String(value); } - const abortCtrl = new AbortController(); let child: ChildProcessWithoutNullStreams; try { const home = homedir(); @@ -101,16 +99,17 @@ export class NodeExtHostMpcService extends ExtHostMcpService { child = spawn(executable, args, { stdio: 'pipe', cwd, - signal: abortCtrl.signal, env, shell, }); } catch (e) { onError(e); - abortCtrl.abort(); return; } + // Create the connection manager for graceful shutdown + const connectionManager = new McpStdioStateHandler(child); + this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Starting }); child.stdout.pipe(new StreamSplitter('\n')).on('data', line => this._proxy.$onDidReceiveMessage(id, line.toString())); @@ -125,22 +124,22 @@ export class NodeExtHostMpcService extends ExtHostMcpService { child.on('spawn', () => this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Running })); child.on('error', e => { - if (abortCtrl.signal.aborted) { + onError(e); + }); + child.on('exit', code => { + this.nodeServers.deleteAndDispose(id); + + if (code === 0 || connectionManager.stopped) { this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Stopped }); } else { - onError(e); - } - }); - child.on('exit', code => - code === 0 || abortCtrl.signal.aborted - ? this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Stopped }) - : this._proxy.$onDidChangeState(id, { + this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Error, message: `Process exited with code ${code}`, - }) - ); + }); + } + }); - this.nodeServers.set(id, { abortCtrl, child }); + this.nodeServers.set(id, connectionManager); } } diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 38239ddfb62..533e0228cbc 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -12,12 +12,11 @@ import { PendingMigrationError, isCancellationError, isSigPipeError, onUnexpecte import { Event } from '../../../base/common/event.js'; import * as performance from '../../../base/common/performance.js'; import { IURITransformer } from '../../../base/common/uriIpc.js'; -import { realpath } from '../../../base/node/extpath.js'; import { Promises } from '../../../base/node/pfs.js'; import { IMessagePassingProtocol } from '../../../base/parts/ipc/common/ipc.js'; import { BufferedEmitter, PersistentProtocol, ProtocolConstants } from '../../../base/parts/ipc/common/ipc.net.js'; import { NodeSocket, WebSocketNodeSocket } from '../../../base/parts/ipc/node/ipc.net.js'; -import type { MessagePortMain } from '../../../base/parts/sandbox/node/electronTypes.js'; +import type { MessagePortMain, MessageEvent as UtilityMessageEvent } from '../../../base/parts/sandbox/node/electronTypes.js'; import { boolean } from '../../../editor/common/config/editorOptions.js'; import product from '../../../platform/product/common/product.js'; import { ExtensionHostMain, IExitFn } from '../common/extensionHostMain.js'; @@ -92,7 +91,7 @@ function patchProcess(allowExit: boolean) { } as (code?: number) => never; // override Electron's process.crash() method - process.crash = function () { + (process as any /* bypass layer checker */).crash = function () { const err = new Error('An extension called process.crash() and this was prevented.'); console.warn(err.stack); }; @@ -167,7 +166,7 @@ function _createExtHostProtocol(): Promise { }); }; - process.parentPort.on('message', (e: Electron.MessageEvent) => withPorts(e.ports)); + (process as unknown as { parentPort: { on: (event: 'message', listener: (messageEvent: UtilityMessageEvent) => void) => void } }).parentPort.on('message', (e: UtilityMessageEvent) => withPorts(e.ports)); }); } else if (extHostConnection.type === ExtHostConnectionType.Socket) { @@ -420,7 +419,7 @@ async function startExtensionHostProcess(): Promise { public readonly pid = process.pid; exit(code: number) { nativeExit(code); } fsExists(path: string) { return Promises.exists(path); } - fsRealpath(path: string) { return realpath(path); } + fsRealpath(path: string) { return Promises.realpath(path); } }; // Attempt to load uri transformer diff --git a/src/vs/workbench/api/node/loopbackServer.ts b/src/vs/workbench/api/node/loopbackServer.ts new file mode 100644 index 00000000000..da3e4f5a549 --- /dev/null +++ b/src/vs/workbench/api/node/loopbackServer.ts @@ -0,0 +1,329 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { randomBytes } from 'crypto'; +import * as http from 'http'; +import { URL } from 'url'; +import { DeferredPromise } from '../../../base/common/async.js'; +import { DEFAULT_AUTH_FLOW_PORT } from '../../../base/common/oauth.js'; +import { URI } from '../../../base/common/uri.js'; +import { ILogger } from '../../../platform/log/common/log.js'; + +export interface IOAuthResult { + code: string; + state: string; +} + +export interface ILoopbackServer { + /** + * The state parameter used in the OAuth flow. + */ + readonly state: string; + + /** + * Starts the server. + * @throws If the server fails to start. + * @throws If the server is already started. + */ + start(): Promise; + + /** + * Stops the server. + * @throws If the server is not started. + * @throws If the server fails to stop. + */ + stop(): Promise; + + /** + * Returns a promise that resolves to the result of the OAuth flow. + */ + waitForOAuthResponse(): Promise; +} + +export class LoopbackAuthServer implements ILoopbackServer { + private readonly _server: http.Server; + private readonly _resultPromise: Promise; + + private _state = randomBytes(16).toString('base64'); + private _port: number | undefined; + + constructor( + private readonly _logger: ILogger, + private readonly _appUri: URI, + private readonly _appName: string + ) { + const deferredPromise = new DeferredPromise(); + this._resultPromise = deferredPromise.p; + + this._server = http.createServer((req, res) => { + const reqUrl = new URL(req.url!, `http://${req.headers.host}`); + switch (reqUrl.pathname) { + case '/': { + const code = reqUrl.searchParams.get('code') ?? undefined; + const state = reqUrl.searchParams.get('state') ?? undefined; + const error = reqUrl.searchParams.get('error') ?? undefined; + if (error) { + res.writeHead(302, { location: `/done?error=${reqUrl.searchParams.get('error_description') || error}` }); + res.end(); + deferredPromise.error(new Error(error)); + break; + } + if (!code || !state) { + res.writeHead(400); + res.end(); + break; + } + if (this.state !== state) { + res.writeHead(302, { location: `/done?error=${encodeURIComponent('State does not match.')}` }); + res.end(); + deferredPromise.error(new Error('State does not match.')); + break; + } + deferredPromise.complete({ code, state }); + res.writeHead(302, { location: '/done' }); + res.end(); + break; + } + // Serve the static files + case '/done': + this._sendPage(res); + break; + default: + res.writeHead(404); + res.end(); + break; + } + }); + } + + get state(): string { return this._state; } + get redirectUri(): string { + if (this._port === undefined) { + throw new Error('Server is not started yet'); + } + return `http://127.0.0.1:${this._port}/`; + } + + private _sendPage(res: http.ServerResponse): void { + const html = this.getHtml(); + res.writeHead(200, { + 'Content-Type': 'text/html', + 'Content-Length': Buffer.byteLength(html, 'utf8') + }); + res.end(html); + } + + start(): Promise { + const deferredPromise = new DeferredPromise(); + if (this._server.listening) { + throw new Error('Server is already started'); + } + const portTimeout = setTimeout(() => { + deferredPromise.error(new Error('Timeout waiting for port')); + }, 5000); + this._server.on('listening', () => { + const address = this._server.address(); + if (typeof address === 'string') { + this._port = parseInt(address); + } else if (address instanceof Object) { + this._port = address.port; + } else { + throw new Error('Unable to determine port'); + } + + clearTimeout(portTimeout); + deferredPromise.complete(); + }); + this._server.on('error', err => { + if ('code' in err && err.code === 'EADDRINUSE') { + this._logger.error('Address in use, retrying with a different port...'); + // Best effort to use a specific port, but fallback to a random one if it is in use + this._server.listen(0, '127.0.0.1'); + return; + } + clearTimeout(portTimeout); + deferredPromise.error(new Error(`Error listening to server: ${err}`)); + }); + this._server.on('close', () => { + deferredPromise.error(new Error('Closed')); + }); + // Best effort to use a specific port, but fallback to a random one if it is in use + this._server.listen(DEFAULT_AUTH_FLOW_PORT, '127.0.0.1'); + return deferredPromise.p; + } + + stop(): Promise { + const deferredPromise = new DeferredPromise(); + if (!this._server.listening) { + deferredPromise.complete(); + return deferredPromise.p; + } + this._server.close((err) => { + if (err) { + deferredPromise.error(err); + } else { + deferredPromise.complete(); + } + }); + // If the server is not closed within 5 seconds, reject the promise + setTimeout(() => { + if (!deferredPromise.isResolved) { + deferredPromise.error(new Error('Timeout waiting for server to close')); + } + }, 5000); + return deferredPromise.p; + } + + waitForOAuthResponse(): Promise { + return this._resultPromise; + } + + getHtml(): string { + // TODO: Bring this in via mixin. Skipping exploration for now. + let backgroundImage = 'url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQABAMAAACNMzawAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAJ1BMVEUAAAD///9Qm8+ozed8tNsWer+Lvd9trNfF3u9Ck8slgsMzi8eZxeM/Qa6mAAAAAXRSTlMAQObYZgAAAAFiS0dEAf8CLd4AAAAHdElNRQfiCwYULRt0g+ZLAAAJRUlEQVR42u3SUY0CQRREUSy0hSZtBA+wfOwv4wAPYwAJSMAfAthkB6YD79HnKqikzmYjSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk6cmKhg4AAASAABAAAkAACAABIAAEgAAQAAJAAAgAAaBvBVAjtV2wfFe1ogcA+0gdFgBoe60IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnAjAP/1MkWoAvBvAsUTqBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAgAASAABIAAEAACQAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKA7gDlbFwC6AijZagAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQDM2boA0BXAqAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIATARAAAkAAvF7N1hWArgBKthoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfBrAlK0bAF0BjBoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EQABIAAEwOtN2boB0BVAyVYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgE8DqNm6AtAVwKgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAEwEQAAJAAPzd7xypMwDvBnAskToBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAIAAkAACAABIAAEgAAQAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAkAACAABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBKBGarsAwK5qRQ8ANGYAACAABIAAEAACQAAIAAEgAASAABAAAkDfCUCSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJC3uDtO80OSql+i8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTExLTA2VDIwOjQ1OjI3KzAwOjAwEjLurQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0xMS0wNlQyMDo0NToyNyswMDowMGNvVhEAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAAAAElFTkSuQmCC\')'; + if (this._appName === 'Visual Studio Code') { + backgroundImage = 'url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgdmlld0JveD0iMCAwIDI1NiAyNTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxtYXNrIGlkPSJtYXNrMCIgbWFzay10eXBlPSJhbHBoYSIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iMCIgeT0iMCIgd2lkdGg9IjI1NiIgaGVpZ2h0PSIyNTYiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4MS41MzQgMjU0LjI1MkMxODUuNTY2IDI1NS44MjMgMTkwLjE2NCAyNTUuNzIyIDE5NC4yMzQgMjUzLjc2NEwyNDYuOTQgMjI4LjQwM0MyNTIuNDc4IDIyNS43MzggMjU2IDIyMC4xMzIgMjU2IDIxMy45ODNWNDIuMDE4MUMyNTYgMzUuODY4OSAyNTIuNDc4IDMwLjI2MzggMjQ2Ljk0IDI3LjU5ODhMMTk0LjIzNCAyLjIzNjgxQzE4OC44OTMgLTAuMzMzMTMyIDE4Mi42NDIgMC4yOTYzNDQgMTc3Ljk1NSAzLjcwNDE4QzE3Ny4yODUgNC4xOTEgMTc2LjY0NyA0LjczNDU0IDE3Ni4wNDkgNS4zMzM1NEw3NS4xNDkgOTcuMzg2MkwzMS4xOTkyIDY0LjAyNDdDMjcuMTA3OSA2MC45MTkxIDIxLjM4NTMgNjEuMTczNSAxNy41ODU1IDY0LjYzTDMuNDg5MzYgNzcuNDUyNUMtMS4xNTg1MyA4MS42ODA1IC0xLjE2Mzg2IDg4Ljk5MjYgMy40Nzc4NSA5My4yMjc0TDQxLjU5MjYgMTI4TDMuNDc3ODUgMTYyLjc3M0MtMS4xNjM4NiAxNjcuMDA4IC0xLjE1ODUzIDE3NC4zMiAzLjQ4OTM2IDE3OC41NDhMMTcuNTg1NSAxOTEuMzdDMjEuMzg1MyAxOTQuODI3IDI3LjEwNzkgMTk1LjA4MSAzMS4xOTkyIDE5MS45NzZMNzUuMTQ5IDE1OC42MTRMMTc2LjA0OSAyNTAuNjY3QzE3Ny42NDUgMjUyLjI2NCAxNzkuNTE5IDI1My40NjcgMTgxLjUzNCAyNTQuMjUyWk0xOTIuMDM5IDY5Ljg4NTNMMTE1LjQ3OSAxMjhMMTkyLjAzOSAxODYuMTE1VjY5Ljg4NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L21hc2s+CjxnIG1hc2s9InVybCgjbWFzazApIj4KPHBhdGggZD0iTTI0Ni45NCAyNy42MzgzTDE5NC4xOTMgMi4yNDEzOEMxODguMDg4IC0wLjY5ODMwMiAxODAuNzkxIDAuNTQxNzIxIDE3NS45OTkgNS4zMzMzMkwzLjMyMzcxIDE2Mi43NzNDLTEuMzIwODIgMTY3LjAwOCAtMS4zMTU0OCAxNzQuMzIgMy4zMzUyMyAxNzguNTQ4TDE3LjQzOTkgMTkxLjM3QzIxLjI0MjEgMTk0LjgyNyAyNi45NjgyIDE5NS4wODEgMzEuMDYxOSAxOTEuOTc2TDIzOS4wMDMgMzQuMjI2OUMyNDUuOTc5IDI4LjkzNDcgMjU1Ljk5OSAzMy45MTAzIDI1NS45OTkgNDIuNjY2N1Y0Mi4wNTQzQzI1NS45OTkgMzUuOTA3OCAyNTIuNDc4IDMwLjMwNDcgMjQ2Ljk0IDI3LjYzODNaIiBmaWxsPSIjMDA2NUE5Ii8+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2QpIj4KPHBhdGggZD0iTTI0Ni45NCAyMjguMzYyTDE5NC4xOTMgMjUzLjc1OUMxODguMDg4IDI1Ni42OTggMTgwLjc5MSAyNTUuNDU4IDE3NS45OTkgMjUwLjY2N0wzLjMyMzcxIDkzLjIyNzJDLTEuMzIwODIgODguOTkyNSAtMS4zMTU0OCA4MS42ODAyIDMuMzM1MjMgNzcuNDUyM0wxNy40Mzk5IDY0LjYyOThDMjEuMjQyMSA2MS4xNzMzIDI2Ljk2ODIgNjAuOTE4OCAzMS4wNjE5IDY0LjAyNDVMMjM5LjAwMyAyMjEuNzczQzI0NS45NzkgMjI3LjA2NSAyNTUuOTk5IDIyMi4wOSAyNTUuOTk5IDIxMy4zMzNWMjEzLjk0NkMyNTUuOTk5IDIyMC4wOTIgMjUyLjQ3OCAyMjUuNjk1IDI0Ni45NCAyMjguMzYyWiIgZmlsbD0iIzAwN0FDQyIvPgo8L2c+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIxX2QpIj4KPHBhdGggZD0iTTE5NC4xOTYgMjUzLjc2M0MxODguMDg5IDI1Ni43IDE4MC43OTIgMjU1LjQ1OSAxNzYgMjUwLjY2N0MxODEuOTA0IDI1Ni41NzEgMTkyIDI1Mi4zODkgMTkyIDI0NC4wMzlWMTEuOTYwNkMxOTIgMy42MTA1NyAxODEuOTA0IC0wLjU3MTE3NSAxNzYgNS4zMzMyMUMxODAuNzkyIDAuNTQxMTY2IDE4OC4wODkgLTAuNzAwNjA3IDE5NC4xOTYgMi4yMzY0OEwyNDYuOTM0IDI3LjU5ODVDMjUyLjQ3NiAzMC4yNjM1IDI1NiAzNS44Njg2IDI1NiA0Mi4wMTc4VjIxMy45ODNDMjU2IDIyMC4xMzIgMjUyLjQ3NiAyMjUuNzM3IDI0Ni45MzQgMjI4LjQwMkwxOTQuMTk2IDI1My43NjNaIiBmaWxsPSIjMUY5Q0YwIi8+CjwvZz4KPGcgc3R5bGU9Im1peC1ibGVuZC1tb2RlOm92ZXJsYXkiIG9wYWNpdHk9IjAuMjUiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4MS4zNzggMjU0LjI1MkMxODUuNDEgMjU1LjgyMiAxOTAuMDA4IDI1NS43MjIgMTk0LjA3NyAyNTMuNzY0TDI0Ni43ODMgMjI4LjQwMkMyNTIuMzIyIDIyNS43MzcgMjU1Ljg0NCAyMjAuMTMyIDI1NS44NDQgMjEzLjk4M1Y0Mi4wMTc5QzI1NS44NDQgMzUuODY4NyAyNTIuMzIyIDMwLjI2MzYgMjQ2Ljc4NCAyNy41OTg2TDE5NC4wNzcgMi4yMzY2NUMxODguNzM3IC0wLjMzMzI5OSAxODIuNDg2IDAuMjk2MTc3IDE3Ny43OTggMy43MDQwMUMxNzcuMTI5IDQuMTkwODMgMTc2LjQ5MSA0LjczNDM3IDE3NS44OTIgNS4zMzMzN0w3NC45OTI3IDk3LjM4NkwzMS4wNDI5IDY0LjAyNDVDMjYuOTUxNyA2MC45MTg5IDIxLjIyOSA2MS4xNzM0IDE3LjQyOTIgNjQuNjI5OEwzLjMzMzExIDc3LjQ1MjNDLTEuMzE0NzggODEuNjgwMyAtMS4zMjAxMSA4OC45OTI1IDMuMzIxNiA5My4yMjczTDQxLjQzNjQgMTI4TDMuMzIxNiAxNjIuNzczQy0xLjMyMDExIDE2Ny4wMDggLTEuMzE0NzggMTc0LjMyIDMuMzMzMTEgMTc4LjU0OEwxNy40MjkyIDE5MS4zN0MyMS4yMjkgMTk0LjgyNyAyNi45NTE3IDE5NS4wODEgMzEuMDQyOSAxOTEuOTc2TDc0Ljk5MjcgMTU4LjYxNEwxNzUuODkyIDI1MC42NjdDMTc3LjQ4OCAyNTIuMjY0IDE3OS4zNjMgMjUzLjQ2NyAxODEuMzc4IDI1NC4yNTJaTTE5MS44ODMgNjkuODg1MUwxMTUuMzIzIDEyOEwxOTEuODgzIDE4Ni4xMTVWNjkuODg1MVoiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPC9nPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2QiIHg9Ii0yMS40ODk2IiB5PSI0MC41MjI1IiB3aWR0aD0iMjk4LjgyMiIgaGVpZ2h0PSIyMzYuMTQ5IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIi8+CjxmZU9mZnNldC8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjEwLjY2NjciLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMjUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJvdmVybGF5IiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3ciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3ciIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfZCIgeD0iMTU0LjY2NyIgeT0iLTIwLjY3MzUiIHdpZHRoPSIxMjIuNjY3IiBoZWlnaHQ9IjI5Ny4zNDciIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiLz4KPGZlT2Zmc2V0Lz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMTAuNjY2NyIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4yNSAwIi8+CjxmZUJsZW5kIG1vZGU9Im92ZXJsYXkiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvdyIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvdyIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyIiB4MT0iMTI3Ljg0NCIgeTE9IjAuNjU5OTg4IiB4Mj0iMTI3Ljg0NCIgeTI9IjI1NS4zNCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K\')'; + } else if (this._appName === 'Visual Studio Code - Insiders') { + backgroundImage = 'url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgdmlld0JveD0iMCAwIDI1NiAyNTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxtYXNrIGlkPSJtYXNrMCIgbWFzay10eXBlPSJhbHBoYSIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iMCIgeT0iMCIgd2lkdGg9IjI1NiIgaGVpZ2h0PSIyNTYiPgo8cGF0aCBkPSJNMTc2LjA0OSAyNTAuNjY5QzE4MC44MzggMjU1LjQ1OSAxODguMTMgMjU2LjcgMTk0LjIzNCAyNTMuNzY0TDI0Ni45NCAyMjguNDE5QzI1Mi40NzggMjI1Ljc1NSAyNTYgMjIwLjE1NCAyNTYgMjE0LjAwOFY0Mi4xNDc5QzI1NiAzNi4wMDI1IDI1Mi40NzggMzAuNDAwOCAyNDYuOTQgMjcuNzM3NEwxOTQuMjM0IDIuMzkwODlDMTg4LjEzIC0wLjU0NDQxNiAxODAuODM4IDAuNjk2NjA3IDE3Ni4wNDkgNS40ODU3MkMxODEuOTUgLTAuNDE1MDYgMTkyLjAzOSAzLjc2NDEzIDE5Mi4wMzkgMTIuMTA5MVYyNDQuMDQ2QzE5Mi4wMzkgMjUyLjM5MSAxODEuOTUgMjU2LjU3IDE3Ni4wNDkgMjUwLjY2OVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xODEuMzc5IDE4MC42NDZMMTE0LjMzIDEyOC42MzNMMTgxLjM3OSA3NS41MTE0VjE3Ljc5NEMxODEuMzc5IDEwLjg0NzcgMTczLjEyOCA3LjIwNjczIDE2Ny45OTYgMTEuODg2Mkw3NC42NTE0IDk3Ljg1MThMMzEuMTk5NCA2NC4xNDM4QzI3LjEwODEgNjEuMDM5IDIxLjM4NTEgNjEuMjk0IDE3LjU4NTMgNjQuNzQ3NkwzLjQ4OTc0IDc3LjU2MjdDLTEuMTU4NDcgODEuNzg5MyAtMS4xNjM2NyA4OS4wOTQ4IDMuNDc2NzIgOTMuMzI5MkwxNjcuOTggMjQ0LjE4NUMxNzMuMTA3IDI0OC44ODcgMTgxLjM3OSAyNDUuMjQ5IDE4MS4zNzkgMjM4LjI5MlYxODAuNjQ2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTM2LjY5MzcgMTM0LjE5NUwzLjQ3NjcyIDE2Mi44MjhDLTEuMTYzNjcgMTY3LjA2MiAtMS4xNTg0NyAxNzQuMzcgMy40ODk3NCAxNzguNTk0TDE3LjU4NTMgMTkxLjQwOUMyMS4zODUxIDE5NC44NjMgMjcuMTA4MSAxOTUuMTE4IDMxLjE5OTQgMTkyLjAxM0w2OS40NDcyIDE2NC4wNTdMMzYuNjkzNyAxMzQuMTk1WiIgZmlsbD0id2hpdGUiLz4KPC9tYXNrPgo8ZyBtYXNrPSJ1cmwoI21hc2swKSI+CjxwYXRoIGQ9Ik0xNjcuOTk2IDExLjg4NTdDMTczLjEyOCA3LjIwNjI3IDE4MS4zNzkgMTAuODQ3MyAxODEuMzc5IDE3Ljc5MzZWNzUuNTEwOUwxMDQuOTM4IDEzNi4wNzNMNjUuNTc0MiAxMDYuMjExTDE2Ny45OTYgMTEuODg1N1oiIGZpbGw9IiMwMDlBN0MiLz4KPHBhdGggZD0iTTM2LjY5MzcgMTM0LjE5NEwzLjQ3NjcyIDE2Mi44MjdDLTEuMTYzNjcgMTY3LjA2MiAtMS4xNTg0NyAxNzQuMzcgMy40ODk3NCAxNzguNTk0TDE3LjU4NTMgMTkxLjQwOUMyMS4zODUxIDE5NC44NjMgMjcuMTA4MSAxOTUuMTE4IDMxLjE5OTQgMTkyLjAxM0w2OS40NDcyIDE2NC4wNTZMMzYuNjkzNyAxMzQuMTk0WiIgZmlsbD0iIzAwOUE3QyIvPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kKSI+CjxwYXRoIGQ9Ik0xODEuMzc5IDE4MC42NDVMMzEuMTk5NCA2NC4xNDI3QzI3LjEwODEgNjEuMDM3OSAyMS4zODUxIDYxLjI5MjkgMTcuNTg1MyA2NC43NDY1TDMuNDg5NzQgNzcuNTYxNkMtMS4xNTg0NyA4MS43ODgyIC0xLjE2MzY3IDg5LjA5MzcgMy40NzY3MiA5My4zMjgxTDE2Ny45NzIgMjQ0LjE3NkMxNzMuMTAyIDI0OC44ODEgMTgxLjM3OSAyNDUuMjQxIDE4MS4zNzkgMjM4LjI4VjE4MC42NDVaIiBmaWxsPSIjMDBCMjk0Ii8+CjwvZz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZCkiPgo8cGF0aCBkPSJNMTk0LjIzMyAyNTMuNzY2QzE4OC4xMyAyNTYuNzAxIDE4MC44MzcgMjU1LjQ2IDE3Ni4wNDggMjUwLjY3MUMxODEuOTQ5IDI1Ni41NzEgMTkyLjAzOSAyNTIuMzkyIDE5Mi4wMzkgMjQ0LjA0N1YxMi4xMTAzQzE5Mi4wMzkgMy43NjUzNSAxODEuOTQ5IC0wLjQxMzgzOSAxNzYuMDQ4IDUuNDg2OTRDMTgwLjgzNyAwLjY5NzgyNCAxODguMTI5IC0wLjU0MzE5MSAxOTQuMjMzIDIuMzkyMUwyNDYuOTM5IDI3LjczODZDMjUyLjQ3OCAzMC40MDIgMjU2IDM2LjAwMzcgMjU2IDQyLjE0OTFWMjE0LjAwOUMyNTYgMjIwLjE1NSAyNTIuNDc4IDIyNS43NTcgMjQ2LjkzOSAyMjguNDJMMTk0LjIzMyAyNTMuNzY2WiIgZmlsbD0iIzI0QkZBNSIvPgo8L2c+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZCIgeD0iLTIxLjMzMzMiIHk9IjQwLjY0MTMiIHdpZHRoPSIyMjQuMDQ1IiBoZWlnaHQ9IjIyNi45ODgiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiLz4KPGZlT2Zmc2V0Lz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMTAuNjY2NyIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4xNSAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIxX2QiIHg9IjE1NC43MTUiIHk9Ii0yMC41MTY5IiB3aWR0aD0iMTIyLjYxOCIgaGVpZ2h0PSIyOTcuMTkxIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIi8+CjxmZU9mZnNldC8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjEwLjY2NjciLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMjUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJvdmVybGF5IiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3ciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3ciIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjwvZGVmcz4KPC9zdmc+Cg==\')'; + } + return ` + + + + + GitHub Authentication - Sign In + + + + + + + ${this._appName} + +
+
+ Sign-in successful! Returning to ${this._appName}... +

+ If you're not redirected automatically, click here or close this page. +
+
+ An error occurred while signing in: +
+
+
+ + +`; + } +} diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index 2e10b368a64..34abb354e35 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -774,8 +774,9 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(values.length, 2); const [first, second] = values; assert.strictEqual(first instanceof types.SymbolInformation, true); - assert.strictEqual(first instanceof types.DocumentSymbol, false); + assert.strictEqual(first instanceof types.DocumentSymbol, true); assert.strictEqual(second instanceof types.SymbolInformation, true); + assert.strictEqual(second instanceof types.DocumentSymbol, true); assert.strictEqual(first.name, 'DocumentSymbol'); assert.strictEqual(first.children.length, 1); assert.strictEqual(second.name, 'SymbolInformation'); diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index 21668a51aa0..004ba6dbe2e 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { TestDialogService } from '../../../../platform/dialogs/test/common/testDialogService.js'; import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js'; -import { IQuickInputHideEvent, IQuickInputService, IQuickPickDidAcceptEvent } from '../../../../platform/quickinput/common/quickInput.js'; +import { IQuickInputHideEvent, IQuickInputService, IQuickPickDidAcceptEvent, IQuickPickItem, QuickInputHideReason } from '../../../../platform/quickinput/common/quickInput.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../platform/telemetry/common/telemetryUtils.js'; @@ -44,27 +43,31 @@ import { TestSecretStorageService } from '../../../../platform/secrets/test/comm import { IDynamicAuthenticationProviderStorageService } from '../../../services/authentication/common/dynamicAuthenticationProviderStorage.js'; import { DynamicAuthenticationProviderStorageService } from '../../../services/authentication/browser/dynamicAuthenticationProviderStorageService.js'; import { ExtHostProgress } from '../../common/extHostProgress.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; class AuthQuickPick { - private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined; + private accept: ((e: IQuickPickDidAcceptEvent) => any) | undefined; + private hide: ((e: IQuickInputHideEvent) => any) | undefined; public items = []; - public get selectedItems(): string[] { + public get selectedItems(): IQuickPickItem[] { return this.items; } onDidAccept(listener: (e: IQuickPickDidAcceptEvent) => any) { - this.listener = listener; + this.accept = listener; } onDidHide(listener: (e: IQuickInputHideEvent) => any) { - + this.hide = listener; } + dispose() { } show() { - this.listener!({ - inBackground: false - }); + this.accept?.({ inBackground: false }); + this.hide?.({ reason: QuickInputHideReason.Other }); } } class AuthTestQuickInputService extends TestQuickInputService { @@ -111,39 +114,43 @@ class TestAuthProvider implements AuthenticationProvider { } suite('ExtHostAuthentication', () => { - let disposables: DisposableStore; + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let extHostAuthentication: ExtHostAuthentication; - let instantiationService: TestInstantiationService; + let mainInstantiationService: TestInstantiationService; - suiteSetup(async () => { - instantiationService = new TestInstantiationService(); - instantiationService.stub(ILogService, new NullLogService()); - instantiationService.stub(IDialogService, new TestDialogService({ confirmed: true })); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(ISecretStorageService, new TestSecretStorageService()); - instantiationService.stub(IDynamicAuthenticationProviderStorageService, instantiationService.createInstance(DynamicAuthenticationProviderStorageService)); - instantiationService.stub(IQuickInputService, new AuthTestQuickInputService()); - instantiationService.stub(IExtensionService, new TestExtensionService()); + setup(async () => { + // services + const services = new ServiceCollection(); + services.set(ILogService, new SyncDescriptor(NullLogService)); + services.set(IDialogService, new SyncDescriptor(TestDialogService, [{ confirmed: true }])); + services.set(IStorageService, new SyncDescriptor(TestStorageService)); + services.set(ISecretStorageService, new SyncDescriptor(TestSecretStorageService)); + services.set(IDynamicAuthenticationProviderStorageService, new SyncDescriptor(DynamicAuthenticationProviderStorageService)); + services.set(IQuickInputService, new SyncDescriptor(AuthTestQuickInputService)); + services.set(IExtensionService, new SyncDescriptor(TestExtensionService)); + services.set(IActivityService, new SyncDescriptor(TestActivityService)); + services.set(IRemoteAgentService, new SyncDescriptor(TestRemoteAgentService)); + services.set(INotificationService, new SyncDescriptor(TestNotificationService)); + services.set(IHostService, new SyncDescriptor(TestHostService)); + services.set(IUserActivityService, new SyncDescriptor(UserActivityService)); + services.set(IAuthenticationAccessService, new SyncDescriptor(AuthenticationAccessService)); + services.set(IAuthenticationService, new SyncDescriptor(AuthenticationService)); + services.set(IAuthenticationUsageService, new SyncDescriptor(AuthenticationUsageService)); + services.set(IAuthenticationExtensionsService, new SyncDescriptor(AuthenticationExtensionsService)); + mainInstantiationService = disposables.add(new TestInstantiationService(services, undefined, undefined, true)); - instantiationService.stub(IActivityService, new TestActivityService()); - instantiationService.stub(IRemoteAgentService, new TestRemoteAgentService()); - instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(IHostService, new TestHostService()); + // stubs // eslint-disable-next-line local/code-no-dangerous-type-assertions - instantiationService.stub(IOpenerService, {} as Partial); - instantiationService.stub(IUserActivityService, new UserActivityService(instantiationService)); - instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); - instantiationService.stub(IProductService, TestProductService); - instantiationService.stub(IAuthenticationAccessService, instantiationService.createInstance(AuthenticationAccessService)); - instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); - instantiationService.stub(IAuthenticationUsageService, instantiationService.createInstance(AuthenticationUsageService)); - const rpcProtocol = new TestRPCProtocol(); + mainInstantiationService.stub(IOpenerService, {} as Partial); + mainInstantiationService.stub(ITelemetryService, NullTelemetryService); + mainInstantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); + mainInstantiationService.stub(IProductService, TestProductService); - instantiationService.stub(IAuthenticationExtensionsService, instantiationService.createInstance(AuthenticationExtensionsService)); - rpcProtocol.set(MainContext.MainThreadAuthentication, instantiationService.createInstance(MainThreadAuthentication, rpcProtocol)); - rpcProtocol.set(MainContext.MainThreadWindow, instantiationService.createInstance(MainThreadWindow, rpcProtocol)); + const rpcProtocol = disposables.add(new TestRPCProtocol()); + + rpcProtocol.set(MainContext.MainThreadAuthentication, disposables.add(mainInstantiationService.createInstance(MainThreadAuthentication, rpcProtocol))); + rpcProtocol.set(MainContext.MainThreadWindow, disposables.add(mainInstantiationService.createInstance(MainThreadWindow, rpcProtocol))); const initData: IExtHostInitDataService = { environment: { appUriScheme: 'test', @@ -161,13 +168,10 @@ suite('ExtHostAuthentication', () => { new ExtHostWindow(initData, rpcProtocol), new ExtHostUrls(rpcProtocol), new ExtHostProgress(rpcProtocol), - new TestLoggerService(), + disposables.add(new TestLoggerService()), + new NullLogService() ); rpcProtocol.set(ExtHostContext.ExtHostAuthentication, extHostAuthentication); - }); - - setup(async () => { - disposables = new DisposableStore(); disposables.add(extHostAuthentication.registerAuthenticationProvider('test', 'test provider', new TestAuthProvider('test'))); disposables.add(extHostAuthentication.registerAuthenticationProvider( 'test-multiple', @@ -176,14 +180,6 @@ suite('ExtHostAuthentication', () => { { supportsMultipleAccounts: true })); }); - suiteTeardown(() => { - instantiationService.dispose(); - }); - - teardown(() => { - disposables.dispose(); - }); - test('createIfNone - true', async () => { const scopes = ['foo']; const session = await extHostAuthentication.getSession( @@ -553,5 +549,215 @@ suite('ExtHostAuthentication', () => { }); + //#endregion + + //#region Race Condition and Sequencing Tests + + test('concurrent operations on same provider are serialized', async () => { + const provider = new TestAuthProvider('concurrent-test'); + const operationOrder: string[] = []; + + // Mock the provider methods to track operation order + const originalCreateSession = provider.createSession.bind(provider); + const originalGetSessions = provider.getSessions.bind(provider); + + provider.createSession = async (scopes) => { + operationOrder.push(`create-start-${scopes[0]}`); + await new Promise(resolve => setTimeout(resolve, 20)); // Simulate async work + const result = await originalCreateSession(scopes); + operationOrder.push(`create-end-${scopes[0]}`); + return result; + }; + + provider.getSessions = async (scopes) => { + const scopeKey = scopes ? scopes[0] : 'all'; + operationOrder.push(`get-start-${scopeKey}`); + await new Promise(resolve => setTimeout(resolve, 10)); // Simulate async work + const result = await originalGetSessions(scopes); + operationOrder.push(`get-end-${scopeKey}`); + return result; + }; + + const disposable = extHostAuthentication.registerAuthenticationProvider('concurrent-test', 'Concurrent Test', provider); + disposables.add(disposable); + + // Start multiple operations simultaneously on the same provider + const promises = [ + extHostAuthentication.getSession(extensionDescription, 'concurrent-test', ['scope1'], { createIfNone: true }), + extHostAuthentication.getSession(extensionDescription, 'concurrent-test', ['scope2'], { createIfNone: true }), + extHostAuthentication.getSession(extensionDescription, 'concurrent-test', ['scope1'], {}) // This should get the existing session + ]; + + await Promise.all(promises); + + // Verify that operations were serialized - no overlapping operations + // Build a map of operation starts to their corresponding ends + const operationPairs: Array<{ start: number; end: number; operation: string }> = []; + + for (let i = 0; i < operationOrder.length; i++) { + const current = operationOrder[i]; + if (current.includes('-start-')) { + const scope = current.split('-start-')[1]; + const operationType = current.split('-start-')[0]; + const endOperation = `${operationType}-end-${scope}`; + const endIndex = operationOrder.indexOf(endOperation, i + 1); + + if (endIndex !== -1) { + operationPairs.push({ + start: i, + end: endIndex, + operation: `${operationType}-${scope}` + }); + } + } + } + + // Verify no operations overlap (serialization) + for (let i = 0; i < operationPairs.length; i++) { + for (let j = i + 1; j < operationPairs.length; j++) { + const op1 = operationPairs[i]; + const op2 = operationPairs[j]; + + // Operations should not overlap - one should completely finish before the other starts + const op1EndsBeforeOp2Starts = op1.end < op2.start; + const op2EndsBeforeOp1Starts = op2.end < op1.start; + + assert.ok(op1EndsBeforeOp2Starts || op2EndsBeforeOp1Starts, + `Operations ${op1.operation} and ${op2.operation} should not overlap. ` + + `Op1: ${op1.start}-${op1.end}, Op2: ${op2.start}-${op2.end}. ` + + `Order: [${operationOrder.join(', ')}]`); + } + } + + // Verify we have the expected operations + assert.ok(operationOrder.includes('create-start-scope1'), 'Should have created session for scope1'); + assert.ok(operationOrder.includes('create-end-scope1'), 'Should have completed creating session for scope1'); + assert.ok(operationOrder.includes('create-start-scope2'), 'Should have created session for scope2'); + assert.ok(operationOrder.includes('create-end-scope2'), 'Should have completed creating session for scope2'); + + // The third call should use getSessions to find the existing scope1 session + assert.ok(operationOrder.includes('get-start-scope1'), 'Should have called getSessions for existing scope1 session'); + assert.ok(operationOrder.includes('get-end-scope1'), 'Should have completed getSessions for existing scope1 session'); + }); + + test('provider registration and immediate disposal race condition', async () => { + const provider = new TestAuthProvider('race-test'); + + // Register and immediately dispose + const disposable = extHostAuthentication.registerAuthenticationProvider('race-test', 'Race Test', provider); + disposable.dispose(); + + // Try to use the provider after disposal - should fail gracefully + try { + await extHostAuthentication.getSession(extensionDescription, 'race-test', ['scope'], { createIfNone: true }); + assert.fail('Should have thrown an error for non-existent provider'); + } catch (error) { + // Expected - provider should be unavailable + assert.ok(error); + } + }); + + test('provider re-registration after proper disposal', async () => { + const provider1 = new TestAuthProvider('reregister-test-1'); + const provider2 = new TestAuthProvider('reregister-test-2'); + + // First registration + const disposable1 = extHostAuthentication.registerAuthenticationProvider('reregister-test', 'Provider 1', provider1); + + // Create a session with first provider + const session1 = await extHostAuthentication.getSession(extensionDescription, 'reregister-test', ['scope'], { createIfNone: true }); + assert.strictEqual(session1?.account.label, 'reregister-test-1'); + + // Dispose first provider + disposable1.dispose(); + + // Re-register with different provider + const disposable2 = extHostAuthentication.registerAuthenticationProvider('reregister-test', 'Provider 2', provider2); + disposables.add(disposable2); + + // Create session with second provider + const session2 = await extHostAuthentication.getSession(extensionDescription, 'reregister-test', ['scope'], { createIfNone: true }); + assert.strictEqual(session2?.account.label, 'reregister-test-2'); + assert.notStrictEqual(session1?.accessToken, session2?.accessToken); + }); + + test('session operations during provider lifecycle changes', async () => { + const provider = new TestAuthProvider('lifecycle-test'); + const disposable = extHostAuthentication.registerAuthenticationProvider('lifecycle-test', 'Lifecycle Test', provider); + + // Start a session creation + const sessionPromise = extHostAuthentication.getSession(extensionDescription, 'lifecycle-test', ['scope'], { createIfNone: true }); + + // Don't dispose immediately - let the session creation start + await new Promise(resolve => setTimeout(resolve, 5)); + + // Dispose the provider while the session creation is likely still in progress + disposable.dispose(); + + // The session creation should complete successfully even if we dispose during the operation + const session = await sessionPromise; + assert.ok(session); + assert.strictEqual(session.account.label, 'lifecycle-test'); + }); + + test('operations on different providers run concurrently', async () => { + const provider1 = new TestAuthProvider('concurrent-1'); + const provider2 = new TestAuthProvider('concurrent-2'); + + let provider1Started = false; + let provider2Started = false; + let provider1Finished = false; + let provider2Finished = false; + let concurrencyVerified = false; + + // Override createSession to track timing + const originalCreate1 = provider1.createSession.bind(provider1); + const originalCreate2 = provider2.createSession.bind(provider2); + + provider1.createSession = async (scopes) => { + provider1Started = true; + await new Promise(resolve => setTimeout(resolve, 20)); + const result = await originalCreate1(scopes); + provider1Finished = true; + return result; + }; + + provider2.createSession = async (scopes) => { + provider2Started = true; + // Provider 2 should start before provider 1 finishes (concurrent execution) + if (provider1Started && !provider1Finished) { + concurrencyVerified = true; + } + await new Promise(resolve => setTimeout(resolve, 10)); + const result = await originalCreate2(scopes); + provider2Finished = true; + return result; + }; + + const disposable1 = extHostAuthentication.registerAuthenticationProvider('concurrent-1', 'Concurrent 1', provider1); + const disposable2 = extHostAuthentication.registerAuthenticationProvider('concurrent-2', 'Concurrent 2', provider2); + disposables.add(disposable1); + disposables.add(disposable2); + + // Start operations on both providers simultaneously + const [session1, session2] = await Promise.all([ + extHostAuthentication.getSession(extensionDescription, 'concurrent-1', ['scope'], { createIfNone: true }), + extHostAuthentication.getSession(extensionDescription, 'concurrent-2', ['scope'], { createIfNone: true }) + ]); + + // Verify both operations completed successfully + assert.ok(session1); + assert.ok(session2); + assert.ok(provider1Started, 'Provider 1 should have started'); + assert.ok(provider2Started, 'Provider 2 should have started'); + assert.ok(provider1Finished, 'Provider 1 should have finished'); + assert.ok(provider2Finished, 'Provider 2 should have finished'); + assert.strictEqual(session1.account.label, 'concurrent-1'); + assert.strictEqual(session2.account.label, 'concurrent-2'); + + // Verify that operations ran concurrently (provider 2 started while provider 1 was still running) + assert.ok(concurrencyVerified, 'Operations should have run concurrently - provider 2 should start while provider 1 is still running'); + }); + //#endregion }); diff --git a/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts new file mode 100644 index 00000000000..09a9a75c1b8 --- /dev/null +++ b/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { TestDialogService } from '../../../../platform/dialogs/test/common/testDialogService.js'; +import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryService } from '../../../../platform/telemetry/common/telemetryUtils.js'; +import { MainThreadAuthentication } from '../../browser/mainThreadAuthentication.js'; +import { ExtHostContext, MainContext } from '../../common/extHost.protocol.js'; +import { IActivityService } from '../../../services/activity/common/activity.js'; +import { AuthenticationService } from '../../../services/authentication/browser/authenticationService.js'; +import { IAuthenticationExtensionsService, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; +import { TestRPCProtocol } from '../common/testRPCProtocol.js'; +import { TestEnvironmentService, TestHostService, TestQuickInputService, TestRemoteAgentService } from '../../../test/browser/workbenchTestServices.js'; +import { TestActivityService, TestExtensionService, TestProductService, TestStorageService } from '../../../test/common/workbenchTestServices.js'; +import { IBrowserWorkbenchEnvironmentService } from '../../../services/environment/browser/environmentService.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { AuthenticationAccessService, IAuthenticationAccessService } from '../../../services/authentication/browser/authenticationAccessService.js'; +import { AuthenticationUsageService, IAuthenticationUsageService } from '../../../services/authentication/browser/authenticationUsageService.js'; +import { AuthenticationExtensionsService } from '../../../services/authentication/browser/authenticationExtensionsService.js'; +import { ILogService, NullLogService } from '../../../../platform/log/common/log.js'; +import { IHostService } from '../../../services/host/browser/host.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IUserActivityService, UserActivityService } from '../../../services/userActivity/common/userActivityService.js'; +import { ISecretStorageService } from '../../../../platform/secrets/common/secrets.js'; +import { TestSecretStorageService } from '../../../../platform/secrets/test/common/testSecretStorageService.js'; +import { IDynamicAuthenticationProviderStorageService } from '../../../services/authentication/common/dynamicAuthenticationProviderStorage.js'; +import { DynamicAuthenticationProviderStorageService } from '../../../services/authentication/browser/dynamicAuthenticationProviderStorageService.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; + +suite('MainThreadAuthentication', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let mainThreadAuthentication: MainThreadAuthentication; + let instantiationService: TestInstantiationService; + let rpcProtocol: TestRPCProtocol; + + setup(async () => { + // services + const services = new ServiceCollection(); + services.set(ILogService, new SyncDescriptor(NullLogService)); + services.set(IDialogService, new SyncDescriptor(TestDialogService, [{ confirmed: true }])); + services.set(IStorageService, new SyncDescriptor(TestStorageService)); + services.set(ISecretStorageService, new SyncDescriptor(TestSecretStorageService)); + services.set(IDynamicAuthenticationProviderStorageService, new SyncDescriptor(DynamicAuthenticationProviderStorageService)); + services.set(IQuickInputService, new SyncDescriptor(TestQuickInputService)); + services.set(IExtensionService, new SyncDescriptor(TestExtensionService)); + services.set(IActivityService, new SyncDescriptor(TestActivityService)); + services.set(IRemoteAgentService, new SyncDescriptor(TestRemoteAgentService)); + services.set(INotificationService, new SyncDescriptor(TestNotificationService)); + services.set(IHostService, new SyncDescriptor(TestHostService)); + services.set(IUserActivityService, new SyncDescriptor(UserActivityService)); + services.set(IAuthenticationAccessService, new SyncDescriptor(AuthenticationAccessService)); + services.set(IAuthenticationService, new SyncDescriptor(AuthenticationService)); + services.set(IAuthenticationUsageService, new SyncDescriptor(AuthenticationUsageService)); + services.set(IAuthenticationExtensionsService, new SyncDescriptor(AuthenticationExtensionsService)); + instantiationService = disposables.add(new TestInstantiationService(services, undefined, undefined, true)); + + // stubs + // eslint-disable-next-line local/code-no-dangerous-type-assertions + instantiationService.stub(IOpenerService, {} as Partial); + instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); + instantiationService.stub(IProductService, TestProductService); + + rpcProtocol = disposables.add(new TestRPCProtocol()); + mainThreadAuthentication = disposables.add(instantiationService.createInstance(MainThreadAuthentication, rpcProtocol)); + rpcProtocol.set(MainContext.MainThreadAuthentication, mainThreadAuthentication); + }); + + test('provider registration completes without errors', async () => { + // Test basic registration - this should complete without throwing + await mainThreadAuthentication.$registerAuthenticationProvider('test-provider', 'Test Provider', false); + + // Test unregistration - this should also complete without throwing + await mainThreadAuthentication.$unregisterAuthenticationProvider('test-provider'); + + // Success if we reach here without timeout + assert.ok(true, 'Registration and unregistration completed successfully'); + }); + + test('event suppression during explicit unregistration', async () => { + let unregisterEventFired = false; + let eventProviderId: string | undefined; + + // Mock the ext host to capture unregister events + const mockExtHost = { + $onDidUnregisterAuthenticationProvider: (id: string) => { + unregisterEventFired = true; + eventProviderId = id; + return Promise.resolve(); + }, + $getSessions: () => Promise.resolve([]), + $createSession: () => Promise.resolve({} as any), + $removeSession: () => Promise.resolve(), + $onDidChangeAuthenticationSessions: () => Promise.resolve(), + $registerDynamicAuthProvider: () => Promise.resolve('test'), + $onDidChangeDynamicAuthProviderTokens: () => Promise.resolve() + }; + rpcProtocol.set(ExtHostContext.ExtHostAuthentication, mockExtHost); + + // Register a provider + await mainThreadAuthentication.$registerAuthenticationProvider('test-suppress', 'Test Suppress', false); + + // Reset the flag + unregisterEventFired = false; + eventProviderId = undefined; + + // Unregister the provider - this should NOT fire the event due to suppression + await mainThreadAuthentication.$unregisterAuthenticationProvider('test-suppress'); + + // Verify the event was suppressed + assert.strictEqual(unregisterEventFired, false, 'Unregister event should be suppressed during explicit unregistration'); + assert.strictEqual(eventProviderId, undefined, 'No provider ID should be captured from suppressed event'); + }); + + test('concurrent provider registrations complete without errors', async () => { + // Register multiple providers simultaneously + const registrationPromises = [ + mainThreadAuthentication.$registerAuthenticationProvider('concurrent-1', 'Concurrent 1', false), + mainThreadAuthentication.$registerAuthenticationProvider('concurrent-2', 'Concurrent 2', false), + mainThreadAuthentication.$registerAuthenticationProvider('concurrent-3', 'Concurrent 3', false) + ]; + + await Promise.all(registrationPromises); + + // Unregister all providers + const unregistrationPromises = [ + mainThreadAuthentication.$unregisterAuthenticationProvider('concurrent-1'), + mainThreadAuthentication.$unregisterAuthenticationProvider('concurrent-2'), + mainThreadAuthentication.$unregisterAuthenticationProvider('concurrent-3') + ]; + + await Promise.all(unregistrationPromises); + + // Success if we reach here without timeout + assert.ok(true, 'Concurrent registrations and unregistrations completed successfully'); + }); +}); diff --git a/src/vs/workbench/api/test/common/testRPCProtocol.ts b/src/vs/workbench/api/test/common/testRPCProtocol.ts index 16a9af44f91..a85d08e4082 100644 --- a/src/vs/workbench/api/test/common/testRPCProtocol.ts +++ b/src/vs/workbench/api/test/common/testRPCProtocol.ts @@ -146,9 +146,7 @@ export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService { }); } - public dispose() { - throw new Error('Not implemented!'); - } + public dispose() { } public assertRegistered(identifiers: ProxyIdentifier[]): void { throw new Error('Not implemented!'); diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts deleted file mode 100644 index 437ca7ff48b..00000000000 --- a/src/vs/workbench/browser/actions.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IAction } from '../../base/common/actions.js'; -import { Disposable, DisposableStore, IDisposable } from '../../base/common/lifecycle.js'; -import { Emitter, Event } from '../../base/common/event.js'; -import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from '../../platform/actions/common/actions.js'; -import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; -import { getActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; - -class MenuActions extends Disposable { - - private readonly menu: IMenu; - - private _primaryActions: IAction[] = []; - get primaryActions() { return this._primaryActions; } - - private _secondaryActions: IAction[] = []; - get secondaryActions() { return this._secondaryActions; } - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - private readonly disposables = this._register(new DisposableStore()); - - constructor( - menuId: MenuId, - private readonly options: IMenuActionOptions | undefined, - private readonly menuService: IMenuService, - private readonly contextKeyService: IContextKeyService - ) { - super(); - - this.menu = this._register(menuService.createMenu(menuId, contextKeyService)); - - this._register(this.menu.onDidChange(() => this.updateActions())); - this.updateActions(); - } - - private updateActions(): void { - this.disposables.clear(); - const newActions = getActionBarActions(this.menu.getActions(this.options)); - this._primaryActions = newActions.primary; - this._secondaryActions = newActions.secondary; - this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {})); - this._onDidChange.fire(); - } - - private updateSubmenus(actions: readonly IAction[], submenus: Record): IDisposable { - const disposables = new DisposableStore(); - - for (const action of actions) { - if (action instanceof SubmenuItemAction && !submenus[action.item.submenu.id]) { - const menu = submenus[action.item.submenu.id] = disposables.add(this.menuService.createMenu(action.item.submenu, this.contextKeyService)); - disposables.add(menu.onDidChange(() => this.updateActions())); - disposables.add(this.updateSubmenus(action.actions, submenus)); - } - } - - return disposables; - } -} - -export class CompositeMenuActions extends Disposable { - - private readonly menuActions: MenuActions; - - private _onDidChange = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - constructor( - readonly menuId: MenuId, - private readonly contextMenuId: MenuId | undefined, - private readonly options: IMenuActionOptions | undefined, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - ) { - super(); - - this.menuActions = this._register(new MenuActions(menuId, this.options, menuService, contextKeyService)); - - this._register(this.menuActions.onDidChange(() => this._onDidChange.fire())); - } - - getPrimaryActions(): IAction[] { - return this.menuActions.primaryActions; - } - - getSecondaryActions(): IAction[] { - return this.menuActions.secondaryActions; - } - - getContextMenuActions(): IAction[] { - if (this.contextMenuId) { - const menu = this.menuService.getMenuActions(this.contextMenuId, this.contextKeyService, this.options); - return getActionBarActions(menu).secondary; - } - - return []; - } -} diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 5d45d2f2cfa..3df2f3426eb 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -20,7 +20,7 @@ import { ILanguageService } from '../../../editor/common/languages/language.js'; import { IFileDialogService, IPickAndOpenOptions } from '../../../platform/dialogs/common/dialogs.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { Schemas } from '../../../base/common/network.js'; -import { IOpenEmptyWindowOptions, IOpenWindowOptions, IWindowOpenable } from '../../../platform/window/common/window.js'; +import { IFileToOpen, IFolderToOpen, IOpenEmptyWindowOptions, IOpenWindowOptions, IWorkspaceToOpen } from '../../../platform/window/common/window.js'; import { IRecent, IWorkspacesService } from '../../../platform/workspaces/common/workspaces.js'; import { IPathService } from '../../services/path/common/pathService.js'; import { ILocalizedString } from '../../../platform/action/common/action.js'; @@ -160,6 +160,7 @@ interface IOpenFolderAPICommandOptions { forceLocalWindow?: boolean; forceProfile?: string; forceTempProfile?: boolean; + filesToOpen?: UriComponents[]; } CommandsRegistry.registerCommand({ @@ -197,8 +198,9 @@ CommandsRegistry.registerCommand({ forceTempProfile: arg?.forceTempProfile, }; - const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; - return commandService.executeCommand('_files.windowOpen', [uriToOpen], options); + const workspaceToOpen: IWorkspaceToOpen | IFolderToOpen = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; + const filesToOpen: IFileToOpen[] = typeof arg === 'object' ? arg.filesToOpen?.map(file => ({ fileUri: URI.from(file, true) })) ?? [] : []; + return commandService.executeCommand('_files.windowOpen', [workspaceToOpen, ...filesToOpen], options); }, metadata: { description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', @@ -213,6 +215,10 @@ CommandsRegistry.registerCommand({ '`forceNewWindow`: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. ' + '`forceReuseWindow`: Whether to force opening the folder/workspace in the same window. Defaults to false. ' + '`noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to false. ' + + '`forceLocalWindow`: Whether to force opening the folder/workspace in a local window. Defaults to false. ' + + '`forceProfile`: The profile to use when opening the folder/workspace. Defaults to the current profile. ' + + '`forceTempProfile`: Whether to use a temporary profile when opening the folder/workspace. Defaults to false. ' + + '`filesToOpen`: An array of files to open in the new window. Defaults to an empty array. ' + 'Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 8be1325c257..d4c3a8b56d5 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -13,7 +13,7 @@ import { IConstructorSignature, IInstantiationService } from '../../platform/ins import { trackFocus, Dimension, IDomPosition } from '../../base/browser/dom.js'; import { IStorageService } from '../../platform/storage/common/storage.js'; import { Disposable } from '../../base/common/lifecycle.js'; -import { assertIsDefined } from '../../base/common/types.js'; +import { assertReturnsDefined } from '../../base/common/types.js'; import { IActionViewItem } from '../../base/browser/ui/actionbar/actionbar.js'; import { MenuId } from '../../platform/actions/common/actions.js'; import { IBoundarySashes } from '../../base/browser/ui/sash/sash.js'; @@ -60,7 +60,7 @@ export abstract class Composite extends Component implements IComposite { } private registerFocusTrackEvents(): { onDidFocus: Emitter; onDidBlur: Emitter } { - const container = assertIsDefined(this.getContainer()); + const container = assertReturnsDefined(this.getContainer()); const focusTracker = this._register(trackFocus(container)); const onDidFocus = this._onDidFocus = this._register(new Emitter()); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 208a96a34ef..bee05148c77 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -30,7 +30,7 @@ import { IStatusbarService } from '../services/statusbar/browser/statusbar.js'; import { IFileService } from '../../platform/files/common/files.js'; import { isCodeEditor } from '../../editor/browser/editorBrowser.js'; import { coalesce } from '../../base/common/arrays.js'; -import { assertIsDefined } from '../../base/common/types.js'; +import { assertReturnsDefined } from '../../base/common/types.js'; import { INotificationService, NotificationsFilter } from '../../platform/notification/common/notification.js'; import { IThemeService } from '../../platform/theme/common/themeService.js'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from '../common/theme.js'; @@ -556,9 +556,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON, position); // Adjust CSS - const activityBarContainer = assertIsDefined(activityBar.getContainer()); - const sideBarContainer = assertIsDefined(sideBar.getContainer()); - const auxiliaryBarContainer = assertIsDefined(auxiliaryBar.getContainer()); + const activityBarContainer = assertReturnsDefined(activityBar.getContainer()); + const sideBarContainer = assertReturnsDefined(sideBar.getContainer()); + const auxiliaryBarContainer = assertReturnsDefined(auxiliaryBar.getContainer()); activityBarContainer.classList.remove(oldPositionValue); sideBarContainer.classList.remove(oldPositionValue); activityBarContainer.classList.add(newPositionValue); @@ -2137,7 +2137,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const newPositionValue = positionToString(position); // Adjust CSS - const panelContainer = assertIsDefined(panelPart.getContainer()); + const panelContainer = assertReturnsDefined(panelPart.getContainer()); panelContainer.classList.remove(oldPositionValue); panelContainer.classList.add(newPositionValue); diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index e85aac5c3cf..cd1bdc2eec0 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -11,7 +11,7 @@ import { IStorageService } from '../../platform/storage/common/storage.js'; import { ISerializableView, IViewSize } from '../../base/browser/ui/grid/grid.js'; import { Event, Emitter } from '../../base/common/event.js'; import { IWorkbenchLayoutService } from '../services/layout/browser/layoutService.js'; -import { assertIsDefined } from '../../base/common/types.js'; +import { assertReturnsDefined } from '../../base/common/types.js'; import { IDisposable, toDisposable } from '../../base/common/lifecycle.js'; export interface IPartOptions { @@ -194,7 +194,7 @@ export abstract class Part extends Component implements ISerializableView { * Layout title and content area in the given dimension. */ protected layoutContents(width: number, height: number): ILayoutContentResult { - const partLayout = assertIsDefined(this.partLayout); + const partLayout = assertReturnsDefined(this.partLayout); return partLayout.layout(width, height); } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index e2726d823d5..080b6871061 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -16,7 +16,7 @@ import { IThemeService, IColorTheme, registerThemingParticipant } from '../../.. import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER } from '../../../common/theme.js'; import { activeContrastBorder, contrastBorder, focusBorder } from '../../../../platform/theme/common/colorRegistry.js'; import { addDisposableListener, append, EventType, isAncestor, $, clearNode } from '../../../../base/browser/dom.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { CustomMenubarControl } from '../titlebar/menubarControl.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { getMenuBarVisibility, MenuSettings } from '../../../../platform/window/common/window.js'; @@ -129,7 +129,7 @@ export class ActivitybarPart extends Part { override updateStyles(): void { super.updateStyles(); - const container = assertIsDefined(this.getContainer()); + const container = assertReturnsDefined(this.getContainer()); const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || ''; container.style.backgroundColor = background; @@ -281,7 +281,7 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { this.menuBarContainer = $('.menubar'); - const content = assertIsDefined(this.element); + const content = assertReturnsDefined(this.element); content.prepend(this.menuBarContainer); // Menubar: install a custom menu bar depending on configuration diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 75a3ce42d68..f62ba06d8b1 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -21,20 +21,16 @@ import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Po import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { IAction, Separator, SubmenuAction, toAction } from '../../../../base/common/actions.js'; import { ToggleAuxiliaryBarAction } from './auxiliaryBarActions.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { LayoutPriority } from '../../../../base/browser/ui/splitview/splitview.js'; import { ToggleSidebarPositionAction } from '../../actions/layoutActions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js'; -import { ActionsOrientation, IActionViewItem, prepareActions } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { ActionsOrientation } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IPaneCompositeBarOptions } from '../paneCompositeBar.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { $ } from '../../../../base/browser/dom.js'; -import { HiddenItemStrategy, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; -import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { CompositeMenuActions } from '../../actions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; interface IAuxiliaryBarPartConfiguration { @@ -159,7 +155,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { override updateStyles(): void { super.updateStyles(); - const container = assertIsDefined(this.getContainer()); + const container = assertReturnsDefined(this.getContainer()); container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || ''; const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder); const isPositionLeft = this.layoutService.getSideBarPosition() === Position.RIGHT; @@ -252,35 +248,6 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { } } - protected override createHeaderArea() { - const headerArea = super.createHeaderArea(); - const globalHeaderContainer = $('.auxiliary-bar-global-header'); - - // Add auxillary header action - const menu = this.headerFooterCompositeBarDispoables.add(this.instantiationService.createInstance(CompositeMenuActions, MenuId.AuxiliaryBarHeader, undefined, undefined)); - - const toolBar = this.headerFooterCompositeBarDispoables.add(this.instantiationService.createInstance(WorkbenchToolBar, globalHeaderContainer, { - actionViewItemProvider: (action, options) => this.headerActionViewItemProvider(action, options), - orientation: ActionsOrientation.HORIZONTAL, - hiddenItemStrategy: HiddenItemStrategy.NoHide, - getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), - })); - - toolBar.setActions(prepareActions(menu.getPrimaryActions())); - this.headerFooterCompositeBarDispoables.add(menu.onDidChange(() => toolBar.setActions(prepareActions(menu.getPrimaryActions())))); - - headerArea.appendChild(globalHeaderContainer); - return headerArea; - } - - private headerActionViewItemProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { - if (action.id === ToggleAuxiliaryBarAction.ID) { - return this.instantiationService.createInstance(ActionViewItem, undefined, action, options); - } - - return undefined; - } - override toJSON(): object { return { type: Parts.AUXILIARYBAR_PART diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 32335eb82c3..099641bd5e3 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -26,7 +26,7 @@ import { IThemeService } from '../../../platform/theme/common/themeService.js'; import { INotificationService } from '../../../platform/notification/common/notification.js'; import { Dimension, append, $, hide, show } from '../../../base/browser/dom.js'; import { AnchorAlignment } from '../../../base/browser/ui/contextview/contextview.js'; -import { assertIsDefined } from '../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../base/common/types.js'; import { createActionViewItem } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; import { AbstractProgressScope, ScopedProgressIndicator } from '../../services/progress/browser/progressIndicator.js'; import { WorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js'; @@ -181,7 +181,7 @@ export abstract class CompositePart extends Part { const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { const that = this; - const compositeProgressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope { + const compositeProgressIndicator = new ScopedProgressIndicator(assertReturnsDefined(this.progressBar), new class extends AbstractProgressScope { constructor() { super(compositeDescriptor!.id, !!isActive); this._register(that.onDidCompositeOpen.event(e => this.onScopeOpened(e.composite.getId()))); @@ -252,7 +252,7 @@ export abstract class CompositePart extends Part { show(compositeContainer); // Setup action runner - const toolBar = assertIsDefined(this.toolBar); + const toolBar = assertReturnsDefined(this.toolBar); toolBar.actionRunner = composite.getActionRunner(); // Update title with composite title if it differs from descriptor @@ -333,7 +333,7 @@ export abstract class CompositePart extends Part { this.titleLabel.updateTitle(compositeId, compositeTitle, keybinding?.getLabel() ?? undefined); - const toolBar = assertIsDefined(this.toolBar); + const toolBar = assertReturnsDefined(this.toolBar); toolBar.setAriaLabel(localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle)); } @@ -345,7 +345,7 @@ export abstract class CompositePart extends Part { const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || []; // Update context - const toolBar = assertIsDefined(this.toolBar); + const toolBar = assertReturnsDefined(this.toolBar); toolBar.context = this.actionsContextProvider(); // Return fn to set into toolbar @@ -455,7 +455,7 @@ export abstract class CompositePart extends Part { super.updateStyles(); // Forward to title label - const titleLabel = assertIsDefined(this.titleLabel); + const titleLabel = assertReturnsDefined(this.titleLabel); titleLabel.updateStyles(); } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 838d74fd56f..a72236d0c57 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -576,7 +576,7 @@ export class BreadcrumbsControl { } } - private _getEditorGroup(data: object): SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined { + private _getEditorGroup(data: unknown): SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined { if (data === BreadcrumbsControl.Payload_RevealAside) { return SIDE_GROUP; } else if (data === BreadcrumbsControl.Payload_Reveal) { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index e01631d1552..fd784e6f69b 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -82,7 +82,7 @@ export abstract class BreadcrumbsPicker { setTimeout(() => this._tree.dispose(), 0); // tree cannot be disposed while being opened... } - async show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise { + async show(input: FileElement | OutlineElement2, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise { const theme = this._themeService.getColorTheme(); const color = theme.getColor(breadcrumbsPickerBackground); diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 9f0b51ba1c2..f604df1d0fe 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -10,7 +10,7 @@ import { renderFormattedText } from '../../../../base/browser/formattedTextRende import { RunOnceScheduler } from '../../../../base/common/async.js'; import { toDisposable } from '../../../../base/common/lifecycle.js'; import { isMacintosh, isWeb } from '../../../../base/common/platform.js'; -import { assertAllDefined, assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsAllDefined, assertReturnsDefined } from '../../../../base/common/types.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -113,7 +113,7 @@ class DropOverlay extends Themable { } override updateStyles(): void { - const overlay = assertIsDefined(this.overlay); + const overlay = assertReturnsDefined(this.overlay); // Overlay drop background overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || ''; @@ -492,7 +492,7 @@ class DropOverlay extends Themable { } // Make sure the overlay is visible now - const overlay = assertIsDefined(this.overlay); + const overlay = assertReturnsDefined(this.overlay); overlay.style.opacity = '1'; // Enable transition after a timeout to prevent initial animation @@ -503,7 +503,7 @@ class DropOverlay extends Themable { } private doPositionOverlay(options: { top: string; left: string; width: string; height: string }): void { - const [container, overlay] = assertAllDefined(this.container, this.overlay); + const [container, overlay] = assertReturnsAllDefined(this.container, this.overlay); // Container const offsetHeight = this.getOverlayOffsetHeight(); @@ -532,7 +532,7 @@ class DropOverlay extends Themable { } private hideOverlay(): void { - const overlay = assertIsDefined(this.overlay); + const overlay = assertReturnsDefined(this.overlay); // Reset overlay this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' }); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index db36961d502..1e8b15256e5 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1036,7 +1036,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { findEditors(resource: URI, options?: IFindEditorOptions): EditorInput[] { const canonicalResource = this.uriIdentityService.asCanonicalUri(resource); - return this.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { + return this.getEditors(options?.order ?? EditorsOrder.SEQUENTIAL).filter(editor => { if (editor.resource && isEqual(editor.resource, canonicalResource)) { return true; } diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index af5df2dd0b0..85ba667b681 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -18,7 +18,7 @@ import { EditorPane } from './editorPane.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditorProgressService, LongRunningOperation } from '../../../../platform/progress/common/progress.js'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS, IInternalEditorOpenOptions } from './editor.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js'; import { ErrorPlaceholderEditor, IErrorEditorPlaceholderOptions, WorkspaceTrustRequiredPlaceholderEditor } from './editorPlaceholder.js'; import { EditorOpenSource, IEditorOptions } from '../../../../platform/editor/common/editor.js'; @@ -317,7 +317,7 @@ export class EditorPanes extends Disposable { return WorkspaceTrustRequiredPlaceholderEditor.DESCRIPTOR; } - return assertIsDefined(this.editorPanesRegistry.getEditorPane(editor)); + return assertReturnsDefined(this.editorPanesRegistry.getEditorPane(editor)); } private doShowEditorPane(descriptor: IEditorPaneDescriptor): EditorPane { @@ -337,7 +337,7 @@ export class EditorPanes extends Disposable { this.doSetActiveEditorPane(editorPane); // Show editor - const container = assertIsDefined(editorPane.getContainer()); + const container = assertReturnsDefined(editorPane.getContainer()); this.editorPanesParent.appendChild(container); show(container); diff --git a/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts b/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts index 4b3ef8b4dc7..06c253a9186 100644 --- a/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts +++ b/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts @@ -18,7 +18,7 @@ import { Dimension, size, clearNode, $, EventHelper } from '../../../../base/bro import { CancellationToken } from '../../../../base/common/cancellation.js'; import { DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { assertAllDefined } from '../../../../base/common/types.js'; +import { assertReturnsAllDefined } from '../../../../base/common/types.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IWorkspaceContextService, isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; import { EditorOpenSource, IEditorOptions } from '../../../../platform/editor/common/editor.js'; @@ -90,7 +90,7 @@ export abstract class EditorPlaceholder extends EditorPane { } private async renderInput(input: EditorInput, options: IEditorOptions | undefined): Promise { - const [container, scrollbar] = assertAllDefined(this.container, this.scrollbar); + const [container, scrollbar] = assertReturnsAllDefined(this.container, this.scrollbar); // Reset any previous contents clearNode(container); @@ -155,7 +155,7 @@ export abstract class EditorPlaceholder extends EditorPane { } layout(dimension: Dimension): void { - const [container, scrollbar] = assertAllDefined(this.container, this.scrollbar); + const [container, scrollbar] = assertReturnsAllDefined(this.container, this.scrollbar); // Pass on to Container size(container, dimension.width, dimension.height); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 52c6462f761..6170eb89808 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,7 +8,7 @@ import { localize, localize2 } from '../../../../nls.js'; import { getWindowById, runAtThisOrScheduleAtNextAnimationFrame } from '../../../../base/browser/dom.js'; import { format, compare, splitLines } from '../../../../base/common/strings.js'; import { extname, basename, isEqual } from '../../../../base/common/resources.js'; -import { areFunctions, assertIsDefined } from '../../../../base/common/types.js'; +import { areFunctions, assertReturnsDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { Action } from '../../../../base/common/actions.js'; import { Language } from '../../../../base/common/platform.js'; @@ -412,13 +412,13 @@ class EditorStatus extends Disposable { } const picks: QuickPickInput[] = [ - assertIsDefined(activeTextEditorControl.getAction(IndentUsingSpaces.ID)), - assertIsDefined(activeTextEditorControl.getAction(IndentUsingTabs.ID)), - assertIsDefined(activeTextEditorControl.getAction(ChangeTabDisplaySize.ID)), - assertIsDefined(activeTextEditorControl.getAction(DetectIndentation.ID)), - assertIsDefined(activeTextEditorControl.getAction(IndentationToSpacesAction.ID)), - assertIsDefined(activeTextEditorControl.getAction(IndentationToTabsAction.ID)), - assertIsDefined(activeTextEditorControl.getAction(TrimTrailingWhitespaceAction.ID)) + assertReturnsDefined(activeTextEditorControl.getAction(IndentUsingSpaces.ID)), + assertReturnsDefined(activeTextEditorControl.getAction(IndentUsingTabs.ID)), + assertReturnsDefined(activeTextEditorControl.getAction(ChangeTabDisplaySize.ID)), + assertReturnsDefined(activeTextEditorControl.getAction(DetectIndentation.ID)), + assertReturnsDefined(activeTextEditorControl.getAction(IndentationToSpacesAction.ID)), + assertReturnsDefined(activeTextEditorControl.getAction(IndentationToTabsAction.ID)), + assertReturnsDefined(activeTextEditorControl.getAction(TrimTrailingWhitespaceAction.ID)) ].map((a: IEditorAction) => { return { id: a.id, diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 4d4573776a7..b0a44e2c23a 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -28,7 +28,7 @@ import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, Sid import { EditorInput } from '../../../common/editor/editorInput.js'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from '../../../common/contextkeys.js'; import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { isFirefox } from '../../../../base/browser/browser.js'; import { isCancellationError } from '../../../../base/common/errors.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; @@ -260,7 +260,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC const editorActions = this.groupView.createEditorActions(this.editorActionsDisposables); this.editorActionsDisposables.add(editorActions.onDidChange(() => this.updateEditorActionsToolbar())); - const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); + const editorActionsToolbar = assertReturnsDefined(this.editorActionsToolbar); const { primary, secondary } = this.prepareEditorActions(editorActions.actions); editorActionsToolbar.setActions(prepareActions(primary), prepareActions(secondary)); } @@ -275,7 +275,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC return; } - const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); + const editorActionsToolbar = assertReturnsDefined(this.editorActionsToolbar); editorActionsToolbar.setActions([], []); } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 88f8e4f9842..9a978f7f848 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -36,7 +36,7 @@ import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNex import { localize } from '../../../../nls.js'; import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView, prepareMoveCopyEditors } from './editor.js'; import { CloseEditorTabAction, UnpinEditorAction } from './editorActions.js'; -import { assertAllDefined, assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsAllDefined, assertReturnsDefined } from '../../../../base/common/types.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { basenameOrAuthority } from '../../../../base/common/resources.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; @@ -224,7 +224,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private updateTabSizing(fromEvent: boolean): void { - const [tabsContainer, tabSizingFixedDisposables] = assertAllDefined(this.tabsContainer, this.tabSizingFixedDisposables); + const [tabsContainer, tabSizingFixedDisposables] = assertReturnsAllDefined(this.tabsContainer, this.tabSizingFixedDisposables); tabSizingFixedDisposables.clear(); @@ -505,7 +505,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.updateTabsControlVisibility(); // Create tabs as needed - const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); + const [tabsContainer, tabsScrollbar] = assertReturnsAllDefined(this.tabsContainer, this.tabsScrollbar); for (let i = tabsContainer.children.length; i < this.tabsModel.count; i++) { tabsContainer.appendChild(this.createTab(i, tabsContainer, tabsScrollbar)); } @@ -592,7 +592,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { if (this.tabsModel.count) { // Remove tabs that got closed - const tabsContainer = assertIsDefined(this.tabsContainer); + const tabsContainer = assertReturnsDefined(this.tabsContainer); while (tabsContainer.children.length > this.tabsModel.count) { // Remove one tab from container (must be the last to keep indexes in order!) @@ -788,7 +788,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private doWithTab(tabIndex: number, editor: EditorInput, fn: (editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { - const tabsContainer = assertIsDefined(this.tabsContainer); + const tabsContainer = assertReturnsDefined(this.tabsContainer); const tabContainer = tabsContainer.children[tabIndex] as HTMLElement; const tabResourceLabel = this.tabResourceLabels.get(tabIndex); const tabLabel = this.tabLabels[tabIndex]; @@ -857,7 +857,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Given a `tabIndex` that is relative to the tabs model // returns the `editorIndex` relative to the entire group - const editor = assertIsDefined(this.tabsModel.getEditorByIndex(tabIndex)); + const editor = assertReturnsDefined(this.tabsModel.getEditorByIndex(tabIndex)); return this.groupView.getIndexOfEditor(editor); } @@ -891,7 +891,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { anchor = this.lastSingleSelectSelectedEditor; } else { // The active editor is the anchor - const activeEditor = assertIsDefined(this.groupView.activeEditor); + const activeEditor = assertReturnsDefined(this.groupView.activeEditor); this.lastSingleSelectSelectedEditor = activeEditor; anchor = activeEditor; } @@ -1331,7 +1331,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { return; } - let newActiveEditor = assertIsDefined(this.groupView.activeEditor); + let newActiveEditor = assertReturnsDefined(this.groupView.activeEditor); // If active editor is bing unselected then find the most recently opened selected editor // that is not the editor being unselected @@ -1352,7 +1352,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private async unselectAllEditors(): Promise { if (this.groupView.selectedEditors.length > 1) { - const activeEditor = assertIsDefined(this.groupView.activeEditor); + const activeEditor = assertReturnsDefined(this.groupView.activeEditor); await this.groupView.setSelection(activeEditor, []); } } @@ -1845,7 +1845,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private doLayoutTabsWrapping(dimensions: IEditorTitleControlDimensions): boolean { - const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorActionsToolbarContainer, this.tabsScrollbar); + const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertReturnsAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorActionsToolbarContainer, this.tabsScrollbar); // Handle wrapping tabs according to setting: // - enabled: only add class if tabs wrap and don't exceed available dimensions @@ -1979,7 +1979,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private doLayoutTabsNonWrapping(options?: IMultiEditorTabsControlLayoutOptions): void { - const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); + const [tabsContainer, tabsScrollbar] = assertReturnsAllDefined(this.tabsContainer, this.tabsScrollbar); // // Synopsis @@ -2123,7 +2123,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private updateTabsControlVisibility(): void { - const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer); + const tabsAndActionsContainer = assertReturnsDefined(this.tabsAndActionsContainer); tabsAndActionsContainer.classList.toggle('empty', !this.visible); // Reset dimensions if hidden @@ -2148,7 +2148,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private getTabAtIndex(tabIndex: number): HTMLElement | undefined { if (tabIndex >= 0) { - const tabsContainer = assertIsDefined(this.tabsContainer); + const tabsContainer = assertReturnsDefined(this.tabsContainer); return tabsContainer.children[tabIndex] as HTMLElement | undefined; } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 691a716cba6..640c0b8bfe8 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -20,7 +20,7 @@ import { IEditorGroup, IEditorGroupsService } from '../../../services/editor/com import { SplitView, Sizing, Orientation } from '../../../../base/browser/ui/splitview/splitview.js'; import { Event, Relay, Emitter } from '../../../../base/common/event.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import { IConfigurationChangeEvent, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { DEFAULT_EDITOR_MIN_DIMENSIONS } from './editor.js'; @@ -157,7 +157,7 @@ export class SideBySideEditor extends AbstractEditorWithViewState this.layoutPane(this.secondaryEditorPane, size), @@ -238,7 +238,7 @@ export class SideBySideEditor extends AbstractEditorWithViewState this.layoutPane(this.primaryEditorPane, size), @@ -328,8 +328,8 @@ export class SideBySideEditor extends AbstractEditorWithViewState { - const titleContainer = assertIsDefined(this.titleContainer); + const titleContainer = assertReturnsDefined(this.titleContainer); // Signal dirty (unless saving) if (editor.isDirty() && !editor.isSaving()) { @@ -224,7 +224,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { } protected handleBreadcrumbsEnablementChange(): void { - const titleContainer = assertIsDefined(this.titleContainer); + const titleContainer = assertReturnsDefined(this.titleContainer); titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); this.redraw(); @@ -280,7 +280,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { } // Clear if there is no editor - const [titleContainer, editorLabel] = assertAllDefined(this.titleContainer, this.editorLabel); + const [titleContainer, editorLabel] = assertReturnsAllDefined(this.titleContainer, this.editorLabel); if (!editor) { titleContainer.classList.remove('dirty'); editorLabel.clear(); diff --git a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts index e7ace5f1af3..44dbef9ffa0 100644 --- a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts @@ -5,7 +5,7 @@ import { localize } from '../../../../nls.js'; import { URI } from '../../../../base/common/uri.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { ITextEditorPane } from '../../../common/editor.js'; import { applyTextEditorOptions } from '../../../common/editor/editorOptions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -83,7 +83,7 @@ export abstract class AbstractTextCodeEditor extends super.setOptions(options); if (options) { - applyTextEditorOptions(options, assertIsDefined(this.editorControl), ScrollType.Smooth); + applyTextEditorOptions(options, assertReturnsDefined(this.editorControl), ScrollType.Smooth); } } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 63b7b0d6f70..3de940f1771 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -5,7 +5,7 @@ import { localize } from '../../../../nls.js'; import { deepClone } from '../../../../base/common/objects.js'; -import { isObject, assertIsDefined } from '../../../../base/common/types.js'; +import { isObject, assertReturnsDefined } from '../../../../base/common/types.js'; import { ICodeEditor, IDiffEditor } from '../../../../editor/browser/editorBrowser.js'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from '../../../../editor/common/config/editorOptions.js'; import { AbstractTextEditor, IEditorConfiguration } from './textEditor.js'; @@ -121,7 +121,7 @@ export class TextDiffEditor extends AbstractTextEditor imp } // Set Editor Model - const control = assertIsDefined(this.diffEditorControl); + const control = assertReturnsDefined(this.diffEditorControl); const resolvedDiffEditorModel = resolvedModel as TextDiffEditorModel; const vm = resolvedDiffEditorModel.textDiffEditorModel ? control.createViewModel(resolvedDiffEditorModel.textDiffEditorModel) : null; @@ -243,7 +243,7 @@ export class TextDiffEditor extends AbstractTextEditor imp super.setOptions(options); if (options) { - applyTextEditorOptions(options, assertIsDefined(this.diffEditorControl), ScrollType.Smooth); + applyTextEditorOptions(options, assertReturnsDefined(this.diffEditorControl), ScrollType.Smooth); } } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index e0a45755595..817d9febf0f 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -7,7 +7,7 @@ import { localize } from '../../../../nls.js'; import { URI } from '../../../../base/common/uri.js'; import { distinct, deepClone } from '../../../../base/common/objects.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { isObject, assertIsDefined } from '../../../../base/common/types.js'; +import { isObject, assertReturnsDefined } from '../../../../base/common/types.js'; import { MutableDisposable } from '../../../../base/common/lifecycle.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IEditorOpenContext, IEditorPaneSelection, EditorPaneSelectionCompareResult, EditorPaneSelectionChangeReason, IEditorPaneWithSelection, IEditorPaneSelectionChangeEvent, IEditorPaneScrollPosition, IEditorPaneWithScrolling } from '../../../common/editor.js'; @@ -248,7 +248,7 @@ export abstract class AbstractTextEditor extends Abs this.updateEditorConfiguration(); // Update aria label on editor - const editorContainer = assertIsDefined(this.editorContainer); + const editorContainer = assertReturnsDefined(this.editorContainer); editorContainer.setAttribute('aria-label', this.computeAriaLabel()); } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index f828e558722..147f439bb35 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { ICodeEditor, IPasteEvent } from '../../../../editor/browser/editorBrowser.js'; import { IEditorOpenContext, isTextEditorViewState } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -67,7 +67,7 @@ export abstract class AbstractTextResourceEditor extends AbstractTextCodeEditor< } // Set Editor Model - const control = assertIsDefined(this.editorControl); + const control = assertReturnsDefined(this.editorControl); const textEditorModel = resolvedModel.textEditorModel; control.setModel(textEditorModel); diff --git a/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts b/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts index 723c97a134c..62cd08fa022 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts @@ -16,6 +16,7 @@ import { IListService, WorkbenchList } from '../../../../platform/list/browser/l import { getNotificationFromContext } from './notificationsCommands.js'; import { NotificationFocusedContext } from '../../../common/contextkeys.js'; import { INotificationViewItem } from '../../../common/notifications.js'; +import { withSeverityPrefix } from '../../../../platform/notification/common/notification.js'; export class NotificationAccessibleView implements IAccessibleViewImplementation { readonly priority = 90; @@ -56,10 +57,10 @@ export class NotificationAccessibleView implements IAccessibleViewImplementation function getContentForNotification(): string | undefined { const notification = getNotificationFromContext(listService); const message = notification?.message.original.toString(); - if (!notification) { + if (!notification || !message) { return; } - return notification.source ? localize('notification.accessibleViewSrc', '{0} Source: {1}', message, notification.source) : localize('notification.accessibleView', '{0}', message); + return withSeverityPrefix(notification.source ? localize('notification.accessibleViewSrc', '{0} Source: {1}', message, notification.source) : message, notification.severity); } const content = getContentForNotification(); if (!content) { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 103e37a3019..d7cea3a5c02 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -22,7 +22,7 @@ import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { ClearAllNotificationsAction, ConfigureDoNotDisturbAction, ToggleDoNotDisturbBySourceAction, HideNotificationsCenterAction, ToggleDoNotDisturbAction } from './notificationsActions.js'; import { IAction, Separator, toAction } from '../../../../base/common/actions.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { assertAllDefined, assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsAllDefined, assertReturnsDefined } from '../../../../base/common/types.js'; import { NotificationsCenterVisibleContext } from '../../../common/contextkeys.js'; import { INotificationService, NotificationsFilter } from '../../../../platform/notification/common/notification.js'; import { mainWindow } from '../../../../base/browser/window.js'; @@ -87,7 +87,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente show(): void { if (this._isVisible) { - const notificationsList = assertIsDefined(this.notificationsList); + const notificationsList = assertReturnsDefined(this.notificationsList); // Make visible notificationsList.show(); @@ -107,7 +107,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente this.updateTitle(); // Make visible - const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); + const [notificationsList, notificationsCenterContainer] = assertReturnsAllDefined(this.notificationsList, this.notificationsCenterContainer); this._isVisible = true; notificationsCenterContainer.classList.add('visible'); notificationsList.show(); @@ -135,7 +135,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } private updateTitle(): void { - const [notificationsCenterTitle, clearAllAction] = assertAllDefined(this.notificationsCenterTitle, this.clearAllAction); + const [notificationsCenterTitle, clearAllAction] = assertReturnsAllDefined(this.notificationsCenterTitle, this.clearAllAction); if (this.model.notifications.length === 0) { notificationsCenterTitle.textContent = localize('notificationsEmpty', "No new notifications"); @@ -245,7 +245,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente let focusEditor = false; // Update notifications list based on event kind - const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); + const [notificationsList, notificationsCenterContainer] = assertReturnsAllDefined(this.notificationsList, this.notificationsCenterContainer); switch (e.kind) { case NotificationChangeType.ADD: notificationsList.updateNotificationsList(e.index, 0, [e.item]); @@ -365,7 +365,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } // Apply to list - const notificationsList = assertIsDefined(this.notificationsList); + const notificationsList = assertReturnsDefined(this.notificationsList); notificationsList.layout(Math.min(maxWidth, availableWidth), Math.min(maxHeight, availableHeight)); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index edb6062b717..5cefc315325 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -14,13 +14,14 @@ import { INotificationViewItem } from '../../../common/notifications.js'; import { NotificationsListDelegate, NotificationRenderer } from './notificationsViewer.js'; import { CopyNotificationMessageAction } from './notificationsActions.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { assertAllDefined } from '../../../../base/common/types.js'; +import { assertReturnsAllDefined } from '../../../../base/common/types.js'; import { NotificationFocusedContext } from '../../../common/contextkeys.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; import { NotificationActionRunner } from './notificationsCommands.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { withSeverityPrefix } from '../../../../platform/notification/common/notification.js'; export interface INotificationsListOptions extends IListOptions { readonly widgetAriaLabel?: string; @@ -131,7 +132,7 @@ export class NotificationsList extends Disposable { } updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) { - const [list, listContainer] = assertAllDefined(this.list, this.listContainer); + const [list, listContainer] = assertReturnsAllDefined(this.list, this.listContainer); const listHasDOMFocus = isAncestorOfActiveElement(listContainer); // Remember focus and relative top of that item @@ -188,7 +189,7 @@ export class NotificationsList extends Disposable { return; } - const [list, listDelegate] = assertAllDefined(this.list, this.listDelegate); + const [list, listDelegate] = assertReturnsAllDefined(this.list, this.listDelegate); list.updateElementHeight(index, listDelegate.getHeight(item)); list.layout(); } @@ -244,27 +245,32 @@ export class NotificationsList extends Disposable { } } -class NotificationAccessibilityProvider implements IListAccessibilityProvider { +export class NotificationAccessibilityProvider implements IListAccessibilityProvider { + constructor( private readonly _options: INotificationsListOptions, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { } + getAriaLabel(element: INotificationViewItem): string { let accessibleViewHint: string | undefined; const keybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); if (this._configurationService.getValue('accessibility.verbosity.notification')) { accessibleViewHint = keybinding ? localize('notificationAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('notificationAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); } + if (!element.source) { - return accessibleViewHint ? localize('notificationAriaLabelHint', "{0}, notification, {1}", element.message.raw, accessibleViewHint) : localize('notificationAriaLabel', "{0}, notification", element.message.raw); + return withSeverityPrefix(accessibleViewHint ? localize('notificationAriaLabelHint', "{0}, notification, {1}", element.message.raw, accessibleViewHint) : localize('notificationAriaLabel', "{0}, notification", element.message.raw), element.severity); } - return accessibleViewHint ? localize('notificationWithSourceAriaLabelHint', "{0}, source: {1}, notification, {2}", element.message.raw, element.source, accessibleViewHint) : localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", element.message.raw, element.source); + return withSeverityPrefix(accessibleViewHint ? localize('notificationWithSourceAriaLabelHint', "{0}, source: {1}, notification, {2}", element.message.raw, element.source, accessibleViewHint) : localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", element.message.raw, element.source), element.severity); } + getWidgetAriaLabel(): string { return this._options.widgetAriaLabel ?? localize('notificationsList', "Notifications List"); } + getRole(): AriaRole { return 'dialog'; // https://github.com/microsoft/vscode/issues/82728 } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index e3c132ba9f9..b74fdf56371 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -18,12 +18,12 @@ import { widgetShadow } from '../../../../platform/theme/common/colorRegistry.js import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { INotificationsToastController } from './notificationsCommands.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { Severity, NotificationsFilter, NotificationPriority } from '../../../../platform/notification/common/notification.js'; +import { Severity, NotificationsFilter, NotificationPriority, withSeverityPrefix } from '../../../../platform/notification/common/notification.js'; import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IntervalCounter } from '../../../../base/common/async.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { NotificationsToastsVisibleContext } from '../../../common/contextkeys.js'; import { mainWindow } from '../../../../base/browser/window.js'; @@ -200,11 +200,11 @@ export class NotificationsToasts extends Themable implements INotificationsToast const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToast, { verticalScrollMode: ScrollbarVisibility.Hidden, widgetAriaLabel: (() => { - if (!item.source) { - return localize('notificationAriaLabel', "{0}, notification", item.message.raw); + return withSeverityPrefix(localize('notificationAriaLabel', "{0}, notification", item.message.raw), item.severity); } - return localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", item.message.raw, item.source); + + return withSeverityPrefix(localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", item.message.raw, item.source), item.severity); })() }); itemDisposables.add(notificationList); @@ -608,7 +608,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast } // Update visibility in DOM - const notificationsToastsContainer = assertIsDefined(this.notificationsToastsContainer); + const notificationsToastsContainer = assertReturnsDefined(this.notificationsToastsContainer); if (visible) { notificationsToastsContainer.appendChild(toast.container); } else { diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index f164e31785e..f309de40ab6 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -29,9 +29,8 @@ import { localize } from '../../../nls.js'; import { CompositeDragAndDropObserver, toggleDropEffect } from '../dnd.js'; import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../common/theme.js'; import { IPartOptions } from '../part.js'; -import { CompositeMenuActions } from '../actions.js'; import { IMenuService, MenuId } from '../../../platform/actions/common/actions.js'; -import { ActionsOrientation, prepareActions } from '../../../base/browser/ui/actionbar/actionbar.js'; +import { ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js'; import { Gesture, EventType as GestureEventType } from '../../../base/browser/touch.js'; import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { IAction, SubmenuAction } from '../../../base/common/actions.js'; @@ -39,7 +38,7 @@ import { Composite } from '../composite.js'; import { ViewsSubMenu } from './views/viewPaneContainer.js'; import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IHoverService } from '../../../platform/hover/browser/hover.js'; -import { HiddenItemStrategy, WorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js'; import { DeferredPromise } from '../../../base/common/async.js'; export enum CompositeBarPosition { @@ -128,8 +127,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart | undefined = undefined; protected contentDimension: Dimension | undefined; @@ -190,15 +189,13 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.onDidOpen(composite))); this._register(this.onDidPaneCompositeClose(this.onDidClose, this)); - this._register(this.globalActions.onDidChange(() => this.updateGlobalToolbarActions())); this._register(this.registry.onDidDeregister((viewletDescriptor: PaneCompositeDescriptor) => { @@ -357,17 +354,19 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.actionViewItemProvider(action, options), - orientation: ActionsOrientation.HORIZONTAL, - getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), - anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), - toggleMenuTitle: localize('moreActions', "More Actions..."), - hoverDelegate: this.toolbarHoverDelegate, - hiddenItemStrategy: HiddenItemStrategy.NoHide - })); - - this.updateGlobalToolbarActions(); + this.globalToolBar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, + globalTitleActionsContainer, + this.globalActionsMenuId, + { + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), + orientation: ActionsOrientation.HORIZONTAL, + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), + toggleMenuTitle: localize('moreActions', "More Actions..."), + hoverDelegate: this.toolbarHoverDelegate, + hiddenItemStrategy: HiddenItemStrategy.NoHide + } + )); return titleArea; } @@ -628,12 +627,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart a.priority - b.priority)[0]; // Background / foreground colors diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index e1a7a7e2f70..0246cd2ad10 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -71,14 +71,14 @@ align-items: center; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-left { +.monaco-workbench .part.titlebar > .titlebar-container.has-center > .titlebar-left { order: 0; width: 20%; flex-grow: 2; justify-content: flex-start; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center { +.monaco-workbench .part.titlebar > .titlebar-container.has-center > .titlebar-center { order: 1; width: 60%; max-width: fit-content; @@ -88,7 +88,7 @@ justify-content: center; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right { +.monaco-workbench .part.titlebar > .titlebar-container.has-center > .titlebar-right { order: 2; width: 20%; min-width: min-content; @@ -96,7 +96,19 @@ justify-content: flex-end; } +.monaco-workbench .part.titlebar > .titlebar-container:not(.has-center) > .titlebar-left { + flex: 1 1 0%; + min-width: 0; +} +.monaco-workbench .part.titlebar > .titlebar-container:not(.has-center) > .titlebar-center { + display: none; +} + +.monaco-workbench .part.titlebar > .titlebar-container:not(.has-center) > .titlebar-right { + flex: 0 0 auto; + padding-left: 16px; /* ensure there is some space between title and controls */ +} /* Window title text */ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 3a2a578e111..94899243d90 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -568,6 +568,8 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.updateLayout(this.lastLayoutDimensions); // layout menubar and other renderings in the titlebar } })); + } else { + reset(this.title); } } @@ -849,17 +851,22 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; - if (hasCustomTitlebar(this.configurationService, this.titleBarStyle)) { - const zoomFactor = getZoomFactor(getWindow(this.element)); - - this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); - this.rootContainer.classList.toggle('counter-zoom', this.preventZoom); - - if (this.customMenubar.value) { - const menubarDimension = new Dimension(0, dimension.height); - this.customMenubar.value.layout(menubarDimension); - } + if (!hasCustomTitlebar(this.configurationService, this.titleBarStyle)) { + return; } + + const zoomFactor = getZoomFactor(getWindow(this.element)); + + this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); + this.rootContainer.classList.toggle('counter-zoom', this.preventZoom); + + if (this.customMenubar.value) { + const menubarDimension = new Dimension(0, dimension.height); + this.customMenubar.value.layout(menubarDimension); + } + + const hasCenter = this.isCommandCenterVisible || this.title.innerText !== ''; + this.rootContainer.classList.toggle('has-center', hasCenter); } focus(): void { diff --git a/src/vs/workbench/browser/parts/views/viewMenuActions.ts b/src/vs/workbench/browser/parts/views/viewMenuActions.ts new file mode 100644 index 00000000000..a6b4d926b5f --- /dev/null +++ b/src/vs/workbench/browser/parts/views/viewMenuActions.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAction } from '../../../../base/common/actions.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { getActionBarActions, PrimaryAndSecondaryActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuId, IMenuActionOptions, IMenuService, IMenu } from '../../../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IViewDescriptorService, ViewContainer, ViewContainerLocationToString } from '../../../common/views.js'; + +export class ViewMenuActions extends Disposable { + + private readonly menu: IMenu; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + constructor( + readonly menuId: MenuId, + private readonly contextMenuId: MenuId | undefined, + private readonly options: IMenuActionOptions | undefined, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, + ) { + super(); + this.menu = this._register(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true })); + this._register(this.menu.onDidChange(() => { + this.actions = undefined; + this._onDidChange.fire(); + })); + } + + private actions: PrimaryAndSecondaryActions | undefined; + private getActions(): PrimaryAndSecondaryActions { + if (!this.actions) { + this.actions = getActionBarActions(this.menu.getActions(this.options)); + } + return this.actions; + } + + getPrimaryActions(): IAction[] { + return this.getActions().primary; + } + + getSecondaryActions(): IAction[] { + return this.getActions().secondary; + } + + getContextMenuActions(): IAction[] { + if (this.contextMenuId) { + const menu = this.menuService.getMenuActions(this.contextMenuId, this.contextKeyService, this.options); + return getActionBarActions(menu).secondary; + } + return []; + } +} + +export class ViewContainerMenuActions extends ViewMenuActions { + constructor( + element: HTMLElement, + viewContainer: ViewContainer, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService menuService: IMenuService, + ) { + const scopedContextKeyService = contextKeyService.createScoped(element); + scopedContextKeyService.createKey('viewContainer', viewContainer.id); + const viewContainerLocationKey = scopedContextKeyService.createKey('viewContainerLocation', ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)); + super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true, renderShortTitle: true }, scopedContextKeyService, menuService); + this._register(scopedContextKeyService); + this._register(Event.filter(viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === viewContainer)(() => viewContainerLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)))); + } +} diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index e8dedb875d8..b14236832cf 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -23,7 +23,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { Extensions as ViewContainerExtensions, IView, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, ViewContainerLocationToString } from '../../../common/views.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { assertIsDefined, PartialExcept } from '../../../../base/common/types.js'; +import { assertReturnsDefined, PartialExcept } from '../../../../base/common/types.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { MenuId, Action2, IAction2Options, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; @@ -40,7 +40,6 @@ import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; import { URI } from '../../../../base/common/uri.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { CompositeMenuActions } from '../../actions.js'; import { IDropdownMenuActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { FilterWidget, IFilterWidgetOptions } from './viewFilter.js'; @@ -55,6 +54,7 @@ import { IListStyles } from '../../../../base/browser/ui/list/listWidget.js'; import { PANEL_BACKGROUND, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_STICKY_SCROLL_BACKGROUND, PANEL_STICKY_SCROLL_BORDER, PANEL_STICKY_SCROLL_SHADOW, SIDE_BAR_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BORDER, SIDE_BAR_STICKY_SCROLL_SHADOW } from '../../../common/theme.js'; import { IAccessibleViewInformationService } from '../../../services/accessibility/common/accessibleViewInformationService.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { ViewMenuActions } from './viewMenuActions.js'; export enum ViewPaneShowActions { /** Show the actions when the view is hovered. This is the default behavior. */ @@ -340,7 +340,7 @@ export abstract class ViewPane extends Pane implements IView { return this._singleViewPaneContainerTitle; } - readonly menuActions: CompositeMenuActions; + readonly menuActions: ViewMenuActions; private progressBar!: ProgressBar; private progressIndicator!: IProgressIndicator; @@ -388,7 +388,7 @@ export abstract class ViewPane extends Pane implements IView { this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)))); const childInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); - this.menuActions = this._register(childInstantiationService.createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true })); + this.menuActions = this._register(childInstantiationService.createInstance(ViewMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true })); this._register(this.menuActions.onDidChange(() => this.updateActions())); } @@ -641,7 +641,7 @@ export abstract class ViewPane extends Pane implements IView { if (this.progressIndicator === undefined) { const that = this; - this.progressIndicator = this._register(new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope { + this.progressIndicator = this._register(new ScopedProgressIndicator(assertReturnsDefined(this.progressBar), new class extends AbstractProgressScope { constructor() { super(that.id, that.isBodyVisible()); this._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id))); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index eaa4e9aae1a..c87738f78b1 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -14,11 +14,11 @@ import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { combinedDisposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import './media/paneviewlet.css'; import * as nls from '../../../../nls.js'; import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { Action2, IAction2Options, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, IAction2Options, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; @@ -29,19 +29,19 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { activeContrastBorder, asCssVariable } from '../../../../platform/theme/common/colorRegistry.js'; import { IThemeService, Themable } from '../../../../platform/theme/common/themeService.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { CompositeMenuActions } from '../../actions.js'; import { CompositeDragAndDropObserver, toggleDropEffect } from '../../dnd.js'; import { ViewPane } from './viewPane.js'; import { IViewletViewOptions } from './viewsViewlet.js'; import { Component } from '../../../common/component.js'; import { PANEL_SECTION_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_HEADER_FOREGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, SIDE_BAR_SECTION_HEADER_FOREGROUND } from '../../../common/theme.js'; -import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, ViewContainer, ViewContainerLocation, ViewContainerLocationToString, ViewVisibilityState } from '../../../common/views.js'; +import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, ViewContainer, ViewContainerLocation, ViewVisibilityState } from '../../../common/views.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { FocusedViewContext } from '../../../common/contextkeys.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { isHorizontal, IWorkbenchLayoutService, LayoutSettings } from '../../../services/layout/browser/layoutService.js'; import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { ViewContainerMenuActions } from './viewMenuActions.js'; export const ViewsSubMenu = new MenuId('Views'); MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { @@ -288,23 +288,6 @@ class ViewPaneDropOverlay extends Themable { } } -class ViewContainerMenuActions extends CompositeMenuActions { - constructor( - element: HTMLElement, - viewContainer: ViewContainer, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService contextKeyService: IContextKeyService, - @IMenuService menuService: IMenuService, - ) { - const scopedContextKeyService = contextKeyService.createScoped(element); - scopedContextKeyService.createKey('viewContainer', viewContainer.id); - const viewContainerLocationKey = scopedContextKeyService.createKey('viewContainerLocation', ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)); - super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true, renderShortTitle: true }, scopedContextKeyService, menuService); - this._register(scopedContextKeyService); - this._register(Event.filter(viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === viewContainer)(() => viewContainerLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)))); - } -} - export class ViewPaneContainer extends Component implements IViewPaneContainer { readonly viewContainer: ViewContainer; @@ -347,7 +330,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { readonly onDidBlurView = this._onDidBlurView.event; get onDidSashChange(): Event { - return assertIsDefined(this.paneview).onDidSashChange; + return assertReturnsDefined(this.paneview).onDidSashChange; } get panes(): ViewPane[] { @@ -363,7 +346,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } private _menuActions?: ViewContainerMenuActions; - get menuActions(): CompositeMenuActions | undefined { + get menuActions(): ViewContainerMenuActions | undefined { return this._menuActions; } @@ -887,7 +870,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const paneItem: IViewPaneItem = { pane, disposable: store }; this.paneItems.splice(index, 0, paneItem); - assertIsDefined(this.paneview).addPane(pane, size, index); + assertReturnsDefined(this.paneview).addPane(pane, size, index); let overlay: ViewPaneDropOverlay | undefined; @@ -1041,7 +1024,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.lastFocusedPane = undefined; } - assertIsDefined(this.paneview).removePane(pane); + assertReturnsDefined(this.paneview).removePane(pane); const [paneItem] = this.paneItems.splice(index, 1); paneItem.disposable.dispose(); @@ -1065,7 +1048,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const [paneItem] = this.paneItems.splice(fromIndex, 1); this.paneItems.splice(toIndex, 0, paneItem); - assertIsDefined(this.paneview).movePane(from, to); + assertReturnsDefined(this.paneview).movePane(from, to); this.viewContainerModel.move(fromViewDescriptor.id, toViewDescriptor.id); @@ -1073,11 +1056,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } resizePane(pane: ViewPane, size: number): void { - assertIsDefined(this.paneview).resizePane(pane, size); + assertReturnsDefined(this.paneview).resizePane(pane, size); } getPaneSize(pane: ViewPane): number { - return assertIsDefined(this.paneview).getPaneSize(pane); + return assertReturnsDefined(this.paneview).getPaneSize(pane); } private updateViewHeaders(): void { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 8aec734eb49..6d42eeaa3d4 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -5,7 +5,7 @@ import { mark } from '../../base/common/performance.js'; import { domContentLoaded, detectFullscreen, getCookieValue, getWindow } from '../../base/browser/dom.js'; -import { assertIsDefined } from '../../base/common/types.js'; +import { assertReturnsDefined } from '../../base/common/types.js'; import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js'; import { ILogService, ConsoleLogger, getLogLevel, ILoggerService, ILogger } from '../../platform/log/common/log.js'; import { ConsoleLogInAutomationLogger } from '../../platform/log/browser/log.js'; @@ -209,7 +209,7 @@ export class BrowserMain extends Disposable { await remoteAuthorityResolverService.resolveAuthority(this.configuration.remoteAuthority); }, openTunnel: async tunnelOptions => { - const tunnel = assertIsDefined(await remoteExplorerService.forward({ + const tunnel = assertReturnsDefined(await remoteExplorerService.forward({ remote: tunnelOptions.remoteAddress, local: tunnelOptions.localAddressPort, name: tunnelOptions.label, diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 3db16dd0d94..c4c2b8e3b29 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,7 +5,7 @@ import { localize } from '../../nls.js'; import { Event } from '../../base/common/event.js'; -import { DeepRequiredNonNullable, assertIsDefined } from '../../base/common/types.js'; +import { DeepRequiredNonNullable, assertReturnsDefined } from '../../base/common/types.js'; import { URI } from '../../base/common/uri.js'; import { Disposable, IDisposable, toDisposable } from '../../base/common/lifecycle.js'; import { ICodeEditorViewState, IDiffEditor, IDiffEditorViewState, IEditor, IEditorViewState } from '../../editor/common/editorCommon.js'; @@ -1290,6 +1290,11 @@ export interface IFindEditorOptions { * as matching, even if the editor is opened in one of the sides. */ supportSideBySide?: SideBySideEditor.PRIMARY | SideBySideEditor.SECONDARY | SideBySideEditor.ANY; + + /** + * The order in which to consider editors for finding. + */ + order?: EditorsOrder; } export interface IMatchEditorOptions { @@ -1553,7 +1558,7 @@ class EditorFactoryRegistry implements IEditorFactoryRegistry { } getFileEditorFactory(): IFileEditorFactory { - return assertIsDefined(this.fileEditorFactory); + return assertReturnsDefined(this.fileEditorFactory); } registerEditorSerializer(editorTypeId: string, ctor: IConstructorSignature): IDisposable { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index ad2d7ebb97e..a43ead4f7e6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -25,6 +25,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { SnippetParser } from '../../../../../editor/contrib/snippet/browser/snippetParser.js'; import { MicrotaskDelay } from '../../../../../base/common/symbols.js'; +import { Schemas } from '../../../../../base/common/network.js'; export class CheckedStates { @@ -388,7 +389,8 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { } asPreviewUri(uri: URI): URI { - return URI.from({ scheme: BulkEditPreviewProvider.Schema, authority: this._instanceId, path: uri.path, query: uri.toString() }); + const path = uri.scheme === Schemas.untitled ? `/${uri.path}` : uri.path; + return URI.from({ scheme: BulkEditPreviewProvider.Schema, authority: this._instanceId, path, query: uri.toString() }); } private async _init() { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 569f84fe774..d96be5941b6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -59,7 +59,7 @@ import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats. import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; +import { ChatEditorInput, shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; @@ -990,49 +990,33 @@ export interface IClearEditingSessionConfirmationOptions { messageOverride?: string; } -export async function showClearEditingSessionConfirmation(editingSession: IChatEditingSession, dialogService: IDialogService, options?: IClearEditingSessionConfirmationOptions): Promise { - const defaultPhrase = localize('chat.startEditing.confirmation.pending.message.default', "Starting a new chat will end your current edit session."); - const defaultTitle = localize('chat.startEditing.confirmation.title', "Start new chat?"); - const phrase = options?.messageOverride ?? defaultPhrase; - const title = options?.titleOverride ?? defaultTitle; - const currentEdits = editingSession.entries.get(); - const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); +// --- Chat Submenus in various Components - const { result } = await dialogService.prompt({ - title, - message: phrase + ' ' + localize('chat.startEditing.confirmation.pending.message.2', "Do you want to keep pending edits to {0} files?", undecidedEdits.length), - type: 'info', - cancelButton: true, - buttons: [ - { - label: localize('chat.startEditing.confirmation.acceptEdits', "Keep & Continue"), - run: async () => { - await editingSession.accept(); - return true; - } - }, - { - label: localize('chat.startEditing.confirmation.discardEdits', "Undo & Continue"), - run: async () => { - await editingSession.reject(); - return true; - } - } - ], - }); +const menuContext = ContextKeyExpr.and( + ChatContextKeys.Setup.hidden.negate(), + ChatContextKeys.Setup.disabled.negate() +); - return Boolean(result); -} +const title = localize('copilot', "Copilot"); -export function shouldShowClearEditingSessionConfirmation(editingSession: IChatEditingSession): boolean { - const currentEdits = editingSession.entries.get(); - const currentEditCount = currentEdits.length; +MenuRegistry.appendMenuItem(MenuId.EditorContext, { + submenu: MenuId.ChatTextEditorMenu, + group: '1_copilot', + title, + when: menuContext +}); - if (currentEditCount) { - const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); - return !!undecidedEdits.length; - } +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + submenu: MenuId.ChatExplorerMenu, + group: '5_copilot', + title, + when: menuContext +}); - return false; -} +MenuRegistry.appendMenuItem(MenuId.TerminalInstanceContext, { + submenu: MenuId.ChatTerminalMenu, + group: '2_copilot', + title, + when: menuContext +}); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 0f42bb1d7b3..8ad0b634488 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -21,14 +21,14 @@ import { IHostService } from '../../../../services/host/browser/host.js'; import { UntitledTextEditorInput } from '../../../../services/untitled/common/untitledTextEditorInput.js'; import { FileEditorInput } from '../../../files/browser/editors/fileEditorInput.js'; import { NotebookEditorInput } from '../../../notebook/common/notebookEditorInput.js'; -import { IChatContextPickService, IChatContextValueItem, IChatContextPickerItem, IChatContextPickerPickItem } from '../chatContextPickService.js'; +import { IChatContextPickService, IChatContextValueItem, IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPicker } from '../chatContextPickService.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IImageVariableEntry, OmittedState } from '../../common/chatModel.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IImageVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { IChatWidget } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; -import { ChatInstructionsPickerPick } from './promptActions/chatAttachInstructionsAction.js'; +import { ChatInstructionsPickerPick } from '../promptSyntax/attachInstructionsAction.js'; export class ChatContextContributions extends Disposable implements IWorkbenchContribution { @@ -65,10 +65,7 @@ class ToolsContextPickerPick implements IChatContextPickerItem { readonly icon: ThemeIcon = Codicon.tools; readonly ordinal = -500; - asPicker(widget: IChatWidget): { - readonly placeholder: string; - readonly picks: Promise<(IChatContextPickerPickItem | IQuickPickSeparator)[]>; - } { + asPicker(widget: IChatWidget): IChatContextPicker { type Pick = IChatContextPickerPickItem & { toolInfo: { ordinal: number; label: string } }; const items: Pick[] = []; @@ -198,10 +195,7 @@ class RelatedFilesContextPickerPick implements IChatContextPickerItem { return this._chatEditingService.hasRelatedFilesProviders() && (Boolean(widget.getInput()) || widget.attachmentModel.fileAttachments.length > 0); } - asPicker(widget: IChatWidget): { - readonly placeholder: string; - readonly picks: Promise<(IChatContextPickerPickItem | IQuickPickSeparator)[]>; - } { + asPicker(widget: IChatWidget): IChatContextPicker { const picks = (async () => { const chatSessionId = widget.viewModel?.sessionId; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 1f1efb69a07..e02e2ba4670 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -19,6 +19,7 @@ import { ITextModelService } from '../../../../../editor/common/services/resolve import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from '../../../../../editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.js'; import { localize, localize2 } from '../../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -37,14 +38,14 @@ import { isSearchTreeFileMatch, isSearchTreeMatch } from '../../../search/browse import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../search/browser/symbolsQuickAccess.js'; import { SearchContext } from '../../../search/common/constants.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IChatRequestVariableEntry, OmittedState } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../chat.js'; import { IChatContextPickerItem, IChatContextPickService, IChatContextValueItem, isChatContextPickerPickItem } from '../chatContextPickService.js'; import { isQuickChat } from '../chatWidget.js'; import { resizeImage } from '../imageUtils.js'; +import { registerPromptActions } from '../promptSyntax/promptFileActions.js'; import { CHAT_CATEGORY } from './chatActions.js'; -import { registerPromptActions } from './promptActions/index.js'; export function registerChatContextActions() { registerAction2(AttachContextAction); @@ -113,7 +114,7 @@ class AttachFileToChatAction extends AttachResourceAction { id: AttachFileToChatAction.ID, title: localize2('workbench.action.chat.attachFile.label', "Add File to Chat"), category: CHAT_CATEGORY, - f1: false, + f1: true, menu: [{ id: MenuId.SearchContext, group: 'z_chat', @@ -211,7 +212,7 @@ class AttachSelectionToChatAction extends Action2 { id: AttachSelectionToChatAction.ID, title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"), category: CHAT_CATEGORY, - f1: false, + f1: true, menu: { id: MenuId.ChatTextEditorMenu, group: 'zContext', @@ -420,7 +421,7 @@ export class AttachContextAction extends Action2 { const quickInputService = accessor.get(IQuickInputService); const quickChatService = accessor.get(IQuickChatService); const instantiationService = accessor.get(IInstantiationService); - + const commandService = accessor.get(ICommandService); const providerOptions: AnythingQuickAccessProviderRunOptions = { additionPicks, @@ -433,7 +434,7 @@ export class AttachContextAction extends Action2 { this._handleContextPick(item.item, widget); } else if (item.item.type === 'pickerPick') { - isDone = await this._handleContextPickerItem(quickInputService, item.item, widget); + isDone = await this._handleContextPickerItem(quickInputService, commandService, item.item, widget); } if (!isDone) { @@ -466,7 +467,6 @@ export class AttachContextAction extends Action2 { const fileService = accessor.get(IFileService); const textModelService = accessor.get(ITextModelService); - const toAttach: IChatRequestVariableEntry[] = []; if (isIQuickPickItemWithResource(pick) && pick.resource) { @@ -532,7 +532,7 @@ export class AttachContextAction extends Action2 { } } - private async _handleContextPickerItem(quickInputService: IQuickInputService, item: IChatContextPickerItem, widget: IChatWidget): Promise { + private async _handleContextPickerItem(quickInputService: IQuickInputService, commandService: ICommandService, item: IChatContextPickerItem, widget: IChatWidget): Promise { const pickerConfig = item.asPicker(widget); @@ -542,7 +542,16 @@ export class AttachContextAction extends Action2 { label: localize('goBack', 'Go back ↩'), alwaysShow: true }; - const extraPicks: QuickPickItem[] = [{ type: 'separator' }, goBackItem]; + const configureItem = pickerConfig.configure ? { + label: pickerConfig.configure.label, + commandId: pickerConfig.configure.commandId, + alwaysShow: true + } : undefined; + const extraPicks: QuickPickItem[] = [{ type: 'separator' }]; + if (configureItem) { + extraPicks.push(configureItem); + } + extraPicks.push(goBackItem); const qp = store.add(quickInputService.createQuickPick({ useSeparators: true })); @@ -597,6 +606,10 @@ export class AttachContextAction extends Action2 { if (selected === goBackItem) { defer.complete(false); } + if (selected === configureItem) { + defer.complete(true); + commandService.executeCommand(configureItem.commandId); + } if (!e.inBackground) { defer.complete(true); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index 8206f1c2585..112327bde07 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -17,10 +17,10 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; -import { AddConfigurationAction } from '../../../mcp/browser/mcpCommands.js'; +import { McpCommandIds } from '../../../mcp/common/mcpCommandIds.js'; import { IMcpRegistry } from '../../../mcp/common/mcpRegistryTypes.js'; import { IMcpServer, IMcpService, McpConnectionState } from '../../../mcp/common/mcpTypes.js'; -import { IToolData, ToolSet, ToolDataSource, ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { ConfigureToolSets } from '../tools/toolSetsContribution.js'; @@ -86,7 +86,7 @@ export async function showToolsPicker( picked: false, }; - const addMcpPick: CallbackPick = { type: 'item', label: localize('addServer', "Add MCP Server..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => commandService.executeCommand(AddConfigurationAction.ID) }; + const addMcpPick: CallbackPick = { type: 'item', label: localize('addServer', "Add MCP Server..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => commandService.executeCommand(McpCommandIds.AddConfiguration) }; const configureToolSetsPick: CallbackPick = { type: 'item', label: localize('configToolSet', "Configure Tool Sets..."), iconClass: ThemeIcon.asClassName(Codicon.gear), pickable: false, run: () => commandService.executeCommand(ConfigureToolSets.ID) }; const addExpPick: CallbackPick = { type: 'item', label: localize('addExtension', "Install Extension..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => extensionWorkbenchService.openSearch('@tag:language-model-tools') }; const addPick: CallbackPick = { diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts deleted file mode 100644 index 6f3e213326a..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assertDefined } from '../../../../../../../../../base/common/types.js'; -import { URI } from '../../../../../../../../../base/common/uri.js'; -import { ICommandService } from '../../../../../../../../../platform/commands/common/commands.js'; -import { IViewsService } from '../../../../../../../../services/views/common/viewsService.js'; -import { IChatWidget, showChatView } from '../../../../../chat.js'; -import { ACTION_ID_NEW_CHAT } from '../../../../chatActions.js'; -import { IAttachInstructionsActionOptions } from '../../../chatAttachInstructionsAction.js'; - -/** - * Options for the {@link attachInstructionsFiles} function. - */ -export interface IAttachOptions { - /** - * Chat widget instance to attach the prompt to. - */ - readonly widget?: IChatWidget; - /** - * Whether to create a new chat session and - * attach the prompt to it. - */ - readonly inNewChat?: boolean; - - readonly viewsService: IViewsService; - readonly commandService: ICommandService; -} - -/** - * Attaches provided instructions to a chat input. - */ -export const attachInstructionsFiles = async ( - files: URI[], - options: IAttachOptions, -): Promise => { - const widget = await getChatWidgetObject(options); - - for (const file of files) { - widget.attachmentModel.promptInstructions.add(file); - } - - return widget; -}; - -/** - * Gets a chat widget based on the provided {@link IAttachInstructionsActionOptions.widget widget} - * reference and the `inNewChat` flag. - * - * @throws if failed to reveal a chat widget. - */ -export const getChatWidgetObject = async ( - options: IAttachOptions, -): Promise => { - const { widget, inNewChat } = options; - - // if a new chat sessions needs to be created, or there is no - // chat widget reference provided, show a chat view, otherwise - // re-use the existing chat widget - if ((inNewChat === true) || (widget === undefined)) { - return await showChat(options, inNewChat); - } - - return widget; -}; - -/** - * Reveals an existing one or creates a new one based on - * the provided `createNew` flag. - */ -const showChat = async ( - options: IAttachOptions, - createNew: boolean = false, -): Promise => { - const { commandService, viewsService } = options; - - if (createNew === true) { - await commandService.executeCommand(ACTION_ID_NEW_CHAT); - } - - const widget = await showChatView(viewsService); - - assertDefined( - widget, - 'Chat widget must be defined.', - ); - - return widget; -}; diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/detachPrompt.ts b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/detachPrompt.ts deleted file mode 100644 index 4f56b99be1b..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/detachPrompt.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IChatWidget } from '../../../../../chat.js'; -import { URI } from '../../../../../../../../../base/common/uri.js'; - -/** - * Options for the {@link detachPrompt} function. - */ -export interface IDetachPromptOptions { - /** - * Chat widget instance to attach the prompt to. - */ - readonly widget: IChatWidget; -} - -/** - * Detaches provided prompts to a chat input. - */ -export const detachPrompt = async ( - file: URI, - options: IDetachPromptOptions, -): Promise => { - const { widget } = options; - - widget - .attachmentModel - .promptInstructions - .remove(file); - - return widget; -}; diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/runPrompt.ts b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/runPrompt.ts deleted file mode 100644 index f3a49ad2f3d..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/runPrompt.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IChatWidget } from '../../../../../chat.js'; -import { getChatWidgetObject } from './attachInstructions.js'; -import { URI } from '../../../../../../../../../base/common/uri.js'; -import { IViewsService } from '../../../../../../../../services/views/common/viewsService.js'; -import { ICommandService } from '../../../../../../../../../platform/commands/common/commands.js'; -import { getPromptCommandName } from '../../../../../../common/promptSyntax/service/promptsService.js'; - -/** - * Options for the {@link runPromptFile} function. - */ -export interface IRunPromptOptions { - /** - * Chat widget instance to attach the prompt to. - */ - readonly widget?: IChatWidget; - /** - * Whether to create a new chat session and - * attach the instructions file to it. - */ - readonly inNewChat?: boolean; - - readonly viewsService: IViewsService; - readonly commandService: ICommandService; -} - -/** - * Return value of the {@link runPromptFile} function. - */ -interface IRunPromptResult { - readonly widget: IChatWidget; -} - -/** - * Runs the prompt file. - */ -export const runPromptFile = async ( - file: URI, - options: IRunPromptOptions, -): Promise => { - - const widget = await getChatWidgetObject(options); - - widget.setInput(`/${getPromptCommandName(file.path)}`); - // submit the prompt immediately - await widget.acceptInput(); - - - return { widget }; -}; diff --git a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts index e23f62baf59..884b4e2aee2 100644 --- a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts +++ b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts @@ -6,12 +6,10 @@ import * as dom from '../../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; -import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../base/common/network.js'; import { basename, dirname } from '../../../../../base/common/resources.js'; -import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; @@ -24,7 +22,7 @@ import { FileKind, IFileService } from '../../../../../platform/files/common/fil import { ILabelService } from '../../../../../platform/label/common/label.js'; import { ResourceLabels } from '../../../../browser/labels.js'; import { ResourceContextKey } from '../../../../common/contextkeys.js'; -import { IChatRequestImplicitVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestImplicitVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatWidgetService } from '../chat.js'; import { ChatAttachmentModel } from '../chatAttachmentModel.js'; @@ -61,9 +59,7 @@ export class ImplicitContextAttachmentWidget extends Disposable { const file = URI.isUri(this.attachment.value) ? this.attachment.value : this.attachment.value!.uri; const range = undefined; - const attachmentTypeName = (this.attachment.isPromptFile === false) - ? file.scheme === Schemas.vscodeNotebookCell ? localize('cell.lowercase', "cell") : localize('file.lowercase', "file") - : localize('prompt.lowercase', "prompt"); + const attachmentTypeName = file.scheme === Schemas.vscodeNotebookCell ? localize('cell.lowercase', "cell") : localize('file.lowercase', "file"); const fileBasename = basename(file); const fileDirname = dirname(file); @@ -74,16 +70,11 @@ export class ImplicitContextAttachmentWidget extends Disposable { const currentFile = localize('openEditor', "Suggested context (current file)"); const title = `${currentFile}\n${uriLabel}`; - const icon = this.attachment.isPromptFile - ? ThemeIcon.fromId(Codicon.bookmark.id) - : undefined; - label.setFile(file, { fileKind: FileKind.FILE, hidePath: true, range, - title, - icon, + title }); this.domNode.ariaLabel = ariaLabel; this.domNode.tabIndex = 0; diff --git a/src/vs/workbench/contrib/chat/browser/attachments/promptInstructions/promptInstructionsCollectionWidget.ts b/src/vs/workbench/contrib/chat/browser/attachments/promptInstructions/promptInstructionsCollectionWidget.ts deleted file mode 100644 index ab0820b7bc8..00000000000 --- a/src/vs/workbench/contrib/chat/browser/attachments/promptInstructions/promptInstructionsCollectionWidget.ts +++ /dev/null @@ -1,200 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../../base/common/uri.js'; -import { Emitter } from '../../../../../../base/common/event.js'; -import { ResourceLabels } from '../../../../../browser/labels.js'; -import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { InstructionsAttachmentWidget } from './promptInstructionsWidget.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { INSTRUCTIONS_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js'; -import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { ChatPromptAttachmentsCollection } from '../../chatAttachmentModel/chatPromptAttachmentsCollection.js'; - -/** - * Widget for a collection of prompt instructions attachments. - * See {@link InstructionsAttachmentWidget}. - */ -export class PromptInstructionsAttachmentsCollectionWidget extends Disposable { - /** - * List of child instruction attachment widgets. - */ - private children: InstructionsAttachmentWidget[] = []; - - /** - * Event that fires when number of attachments change - * - * See {@link onAttachmentsChange}. - */ - private _onAttachmentsChange = this._register(new Emitter()); - /** - * Subscribe to the event that fires when number of attachments change. - */ - public readonly onAttachmentsChange = this._onAttachmentsChange.event; - - /** - * The parent DOM node this widget was rendered into. - */ - private parentNode: HTMLElement | undefined; - - /** - * Get all `URI`s of all valid references, including all - * the possible references nested inside the children. - */ - public get references(): readonly URI[] { - return this.model.references; - } - - /** - * Get the list of all prompt instruction attachment variables, including all - * nested child references of each attachment explicitly attached by user. - */ - public get chatAttachments() { - return this.model.chatAttachments; - } - - /** - * Get a promise that resolves when parsing/resolving processes - * are fully completed, including all possible nested child references. - */ - public allSettled() { - return this.model.allSettled(); - } - - /** - * Check if child widget list is empty (no attachments present). - */ - public get empty(): boolean { - return this.children.length === 0; - } - - /** - * Check if any of the attachments is a prompt file. - */ - public get hasInstructions(): boolean { - return this.references.some((uri) => { - const model = this.modelService.getModel(uri); - const languageId = model ? model.getLanguageId() : this.languageService.guessLanguageIdByFilepathOrFirstLine(uri); - return languageId === INSTRUCTIONS_LANGUAGE_ID; - }); - } - - constructor( - private readonly model: ChatPromptAttachmentsCollection, - private readonly resourceLabels: ResourceLabels, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @ILanguageService private readonly languageService: ILanguageService, - @IModelService private readonly modelService: IModelService, - @ILogService private readonly logService: ILogService, - ) { - super(); - - // when a new attachment model is added, create a new child widget for it - this._register(this.model.onAdd((attachment) => { - const widget = this.instantiationService.createInstance( - InstructionsAttachmentWidget, - attachment, - this.resourceLabels, - ); - - // handle the child widget disposal event, removing it from the list - widget.onDispose(this.handleAttachmentDispose.bind(this, widget)); - - // register the new child widget - this.children.push(widget); - - // if parent node is present - append the widget to it, otherwise wait - // until the `render` method will be called - if (this.parentNode) { - this.parentNode.appendChild(widget.domNode); - } - - // fire the event to notify about the change in the number of attachments - this._onAttachmentsChange.fire(); - })); - } - - /** - * Handle child widget disposal. - * @param widget The child widget that was disposed. - */ - public handleAttachmentDispose(widget: InstructionsAttachmentWidget): this { - // common prefix for all log messages - const logPrefix = `[onChildDispose] Widget for instructions attachment '${widget.uri.path}'`; - - // flag to check if the widget was found in the children list - let widgetExists = false; - - // filter out disposed child widget from the list - this.children = this.children.filter((child) => { - if (child === widget) { - // because we filter out all objects here it might be ok to have multiple of them, but - // it also highlights a potential issue in our logic somewhere else, so trace a warning here - if (widgetExists) { - this.logService.warn( - `${logPrefix} is present in the children references list multiple times.`, - ); - } - - widgetExists = true; - return false; - } - - return true; - }); - - // no widget was found in the children list, while it might be ok it also - // highlights a potential issue in our logic, so trace a warning here - if (!widgetExists) { - this.logService.warn( - `${logPrefix} was disposed, but was not found in the child references.`, - ); - } - - if (!this.parentNode) { - this.logService.warn( - `${logPrefix} no parent node reference found.`, - ); - } - - // remove the child widget root node from the DOM - this.parentNode?.removeChild(widget.domNode); - - // fire the event to notify about the change in the number of attachments - this._onAttachmentsChange.fire(); - - return this; - } - - /** - * Render attachments into the provided `parentNode`. - * - * Note! this method assumes that the provided `parentNode` is cleared by the caller. - */ - public render( - parentNode: HTMLElement, - ): this { - this.parentNode = parentNode; - - for (const widget of this.children) { - this.parentNode.appendChild(widget.domNode); - } - - return this; - } - - /** - * Dispose of the widget, including all the child widget instances. - */ - public override dispose(): void { - for (const child of this.children) { - child.dispose(); - } - - super.dispose(); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/attachments/promptInstructions/promptInstructionsWidget.ts b/src/vs/workbench/contrib/chat/browser/attachments/promptInstructions/promptInstructionsWidget.ts deleted file mode 100644 index f8612294ad9..00000000000 --- a/src/vs/workbench/contrib/chat/browser/attachments/promptInstructions/promptInstructionsWidget.ts +++ /dev/null @@ -1,176 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from '../../../../../../nls.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import * as dom from '../../../../../../base/browser/dom.js'; -import { ResourceLabels } from '../../../../../browser/labels.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import { ResourceContextKey } from '../../../../../common/contextkeys.js'; -import { Button } from '../../../../../../base/browser/ui/button/button.js'; -import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; -import { basename, dirname } from '../../../../../../base/common/resources.js'; -import { ILabelService } from '../../../../../../platform/label/common/label.js'; -import { StandardMouseEvent } from '../../../../../../base/browser/mouseEvent.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; -import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; -import { FileKind, IFileService } from '../../../../../../platform/files/common/files.js'; -import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; -import { getCleanPromptName } from '../../../../../../platform/prompts/common/prompts.js'; -import { ObservableDisposable } from '../../../../../../base/common/observableDisposable.js'; -import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { ChatPromptAttachmentModel } from '../../chatAttachmentModel/chatPromptAttachmentModel.js'; -import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; -import { getDefaultHoverDelegate } from '../../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; -import { getFlatContextMenuActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; - -/** - * Widget for a single prompt instructions attachment. - */ -export class InstructionsAttachmentWidget extends ObservableDisposable { - /** - * The root DOM node of the widget. - */ - public readonly domNode: HTMLElement; - - /** - * Get the `URI` associated with the model reference. - */ - public get uri(): URI { - return this.model.reference.uri; - } - - /** - * Temporary disposables used for rendering purposes. - */ - private readonly renderDisposables = this._register(new DisposableStore()); - - constructor( - private readonly model: ChatPromptAttachmentModel, - private readonly resourceLabels: ResourceLabels, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IHoverService private readonly hoverService: IHoverService, - @ILabelService private readonly labelService: ILabelService, - @IMenuService private readonly menuService: IMenuService, - @IFileService private readonly fileService: IFileService, - @ILanguageService private readonly languageService: ILanguageService, - @IModelService private readonly modelService: IModelService, - ) { - super(); - - this.domNode = dom.$('.chat-prompt-attachment.chat-attached-context-attachment.show-file-icons.implicit'); - - this._register(this.model.onUpdate(this.render.bind(this))); - this._register(this.model.onDispose(this.dispose.bind(this))); - - this.render(); - } - - /** - * Render this widget. - */ - private render() { - dom.clearNode(this.domNode); - this.renderDisposables.clear(); - this.domNode.classList.remove('warning', 'error', 'disabled'); - - const { topError } = this.model; - - const label = this.resourceLabels.create(this.domNode, { supportIcons: true }); - const file = this.model.reference.uri; - - const fileBasename = basename(file); - const fileDirname = dirname(file); - const friendlyName = `${fileBasename} ${fileDirname}`; - const isPrompt = this.languageService.guessLanguageIdByFilepathOrFirstLine(file) === 'prompt'; - const ariaLabel = isPrompt - ? localize('chat.promptAttachment', "Prompt file, {0}", friendlyName) - : localize('chat.instructionsAttachment', "Instructions attachment, {0}", friendlyName); - const typeLabel = isPrompt - ? localize('prompt', "Prompt") - : localize('instructions', "Instructions"); - - - const uriLabel = this.labelService.getUriLabel(file, { relative: true }); - - let title = `${typeLabel} ${uriLabel}`; - - // if there are some errors/warning during the process of resolving - // attachment references (including all the nested child references), - // add the issue details in the hover title for the attachment, one - // error/warning at a time because there is a limited space available - if (topError) { - const { errorSubject: subject } = topError; - const isError = (subject === 'root'); - - this.domNode.classList.add( - (isError) ? 'error' : 'warning', - ); - - const severity = (isError) - ? localize('error', "Error") - : localize('warning', "Warning"); - - title += `\n[${severity}]: ${topError.localizedMessage}`; - } - - const fileWithoutExtension = getCleanPromptName(file); - label.setFile(URI.file(fileWithoutExtension), { - fileKind: FileKind.FILE, - hidePath: true, - range: undefined, - title, - icon: ThemeIcon.fromId(Codicon.bookmark.id), - extraClasses: [], - }); - this.domNode.ariaLabel = ariaLabel; - this.domNode.tabIndex = 0; - - const hintElement = dom.append(this.domNode, dom.$('span.chat-implicit-hint', undefined, typeLabel)); - this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), hintElement, title)); - - // create the `remove` button - const removeButton = this.renderDisposables.add( - new Button( - this.domNode, - { - supportIcons: true, - title: localize('remove', "Remove"), - }, - ), - ); - - removeButton.icon = Codicon.close; - this.renderDisposables.add(removeButton.onDidClick((e) => { - e.stopPropagation(); - this.model.dispose(); - })); - - // context menu - const scopedContextKeyService = this.renderDisposables.add(this.contextKeyService.createScoped(this.domNode)); - - const resourceContextKey = this.renderDisposables.add( - new ResourceContextKey(scopedContextKeyService, this.fileService, this.languageService, this.modelService), - ); - resourceContextKey.set(file); - - this.renderDisposables.add(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, async domEvent => { - const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent); - dom.EventHelper.stop(domEvent, true); - - this.contextMenuService.showContextMenu({ - contextKeyService: scopedContextKeyService, - getAnchor: () => event, - getActions: () => { - const menu = this.menuService.getMenuActions(MenuId.ChatInputResourceAttachmentContext, scopedContextKeyService, { arg: file }); - return getFlatContextMenuActions(menu); - }, - }); - })); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 705ee660983..a4fa1aabba7 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -20,8 +20,8 @@ import { SyncDescriptor } from '../../../../platform/instantiation/common/descri import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; -import { PromptsConfig } from '../../../../platform/prompts/common/config.js'; -import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, MODE_DEFAULT_SOURCE_FOLDER, MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../../platform/prompts/common/prompts.js'; +import { PromptsConfig } from '../common/promptSyntax/config/config.js'; +import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, MODE_DEFAULT_SOURCE_FOLDER, MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../common/promptSyntax/config/promptFileLocations.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; import { Extensions, IConfigurationMigrationRegistry } from '../../../common/configuration.js'; @@ -48,10 +48,10 @@ import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } f import { ILanguageModelsService, LanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; -import { INSTRUCTIONS_DOCUMENTATION_URL, MODE_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../common/promptSyntax/constants.js'; -import { registerPromptFileContributions } from '../common/promptSyntax/contributions/index.js'; -import { PromptsService } from '../common/promptSyntax/service/promptsService.js'; -import { IPromptsService } from '../common/promptSyntax/service/types.js'; +import { INSTRUCTIONS_DOCUMENTATION_URL, MODE_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../common/promptSyntax/promptTypes.js'; +import { registerPromptFileContributions } from '../common/promptSyntax/promptFileContributions.js'; +import { PromptsService } from '../common/promptSyntax/service/promptsServiceImpl.js'; +import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { LanguageModelToolsExtensionPointHandler } from '../common/tools/languageModelToolsContribution.js'; import { BuiltinToolsContribution } from '../common/tools/tools.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; @@ -73,7 +73,6 @@ import { registerQuickChatActions } from './actions/chatQuickInputActions.js'; import { registerChatTitleActions } from './actions/chatTitleActions.js'; import { registerChatToolActions } from './actions/chatToolActions.js'; import { ChatTransferContribution } from './actions/chatTransfer.js'; -import { SAVE_TO_PROMPT_SLASH_COMMAND_NAME, runSaveToPromptAction } from './actions/promptActions/chatSaveToPromptAction.js'; import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService, IQuickChatService } from './chat.js'; import { ChatAccessibilityService } from './chatAccessibilityService.js'; import './chatAttachmentModel.js'; @@ -97,7 +96,7 @@ import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; import { ChatSetupContribution } from './chatSetup.js'; import { ChatStatusBarEntry } from './chatStatus.js'; import { ChatVariablesService } from './chatVariables.js'; -import { ChatWidgetService } from './chatWidget.js'; +import { ChatWidget, ChatWidgetService } from './chatWidget.js'; import { ChatCodeBlockContextProviderService } from './codeBlockContextProviderService.js'; import { ChatImplicitContextContribution } from './contrib/chatImplicitContext.js'; import './contrib/chatInputCompletions.js'; @@ -105,12 +104,13 @@ import './contrib/chatInputEditorContrib.js'; import './contrib/chatInputEditorHover.js'; import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js'; import { LanguageModelToolsService } from './languageModelToolsService.js'; -import './promptSyntax/contributions/createPromptCommand/createPromptCommand.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; import { registerAction2 } from '../../../../platform/actions/common/actions.js'; import product from '../../../../platform/product/common/product.js'; import { ChatModeService, IChatModeService } from '../common/chatModes.js'; import { ChatResponseResourceFileSystemProvider } from '../common/chatResponseResourceFileSystemProvider.js'; +import { runSaveToPromptAction, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; +import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -239,6 +239,12 @@ configurationRegistry.registerConfiguration({ type: 'boolean', tags: ['experimental'] }, + 'chat.undoRequests.restoreInput': { + default: true, + markdownDescription: nls.localize('chat.undoRequests.restoreInput', "Controls whether the input of the chat should be restored when an undo request is made. The input will be filled with the text of the request that was restored."), + type: 'boolean', + tags: ['experimental'] + }, [mcpEnabledSection]: { type: 'boolean', description: nls.localize('chat.mcp.enabled', "Enables integration with Model Context Protocol servers to provide additional tools and functionality."), @@ -303,13 +309,10 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize('chat.extensionToolsEnabled', "Enable using tools contributed by third-party extensions."), default: true, - tags: ['preview'], policy: { name: 'ChatAgentExtensionTools', minimumVersion: '1.99', description: nls.localize('chat.extensionToolsPolicy', "Enable using tools contributed by third-party extensions."), - previewFeature: true, - defaultValue: false } }, [ChatConfiguration.AgentEnabled]: { @@ -766,3 +769,5 @@ registerPromptFileContributions(); registerWorkbenchContribution2(UserToolSetsContributions.ID, UserToolSetsContributions, WorkbenchPhase.Eventually); registerAction2(ConfigureToolSets); + +ChatWidget.CONTRIBS.push(ChatDynamicVariableModel); diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts index 37cd071116e..8a9468f6ccd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts @@ -88,7 +88,17 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider(); private _onDidChange = this._register(new Emitter()); @@ -42,7 +42,8 @@ export class ChatAttachmentModel extends Disposable { ) { super(); - this.promptInstructions = this._register(instaService.createInstance(ChatPromptAttachmentsCollection)); + this._promptInstructions = this._register(instaService.createInstance(ChatPromptAttachmentsCollection)); + this._register(this._promptInstructions.onUpdate(variable => this._onDidChange.fire({ updated: [variable], deleted: [], added: [] }))); } get attachments(): ReadonlyArray { @@ -74,6 +75,23 @@ export class ChatAttachmentModel extends Disposable { } } + addPromptFiles(promptFiles: readonly URI[]): void { + const variables = promptFiles.map(uri => toPromptFileVariableEntry(uri, true)); + this.addContext(...variables); + } + + hasPromptFiles(languageId: string): boolean { + return this._promptInstructions.hasPromptFiles(languageId); + } + + /** + * Get the list of all prompt instruction attachment variables, including all + * nested child references of each attachment explicitly attached by user. + */ + getPromptFileVariables(): Promise { + return this._promptInstructions.getAttachments(); + } + addFolder(uri: URI) { this.addContext({ kind: 'directory', @@ -84,14 +102,22 @@ export class ChatAttachmentModel extends Disposable { } clear(clearStickyAttachments: boolean = false): void { - const deleted = Array.from(this._attachments.keys()); - this._attachments.clear(); - if (clearStickyAttachments) { - this.promptInstructions.clear(); + const deleted = Array.from(this._attachments.keys()); + this._attachments.clear(); + this._promptInstructions.clear(); + this._onDidChange.fire({ deleted, added: [], updated: [] }); + } else { + const deleted: string[] = []; + const allIds = Array.from(this._attachments.keys()); + for (const id of allIds) { + const entry = this._attachments.get(id); + if (entry && !isPromptFileVariableEntry(entry)) { + this._attachments.delete(id); + } + } + this._onDidChange.fire({ deleted, added: [], updated: [] }); } - - this._onDidChange.fire({ deleted, added: [], updated: [] }); } addContext(...attachments: IChatRequestVariableEntry[]) { @@ -113,24 +139,24 @@ export class ChatAttachmentModel extends Disposable { const updated: IChatRequestVariableEntry[] = []; for (const id of toDelete) { - if (this._attachments.delete(id)) { + const item = this._attachments.get(id); + if (item) { + this._attachments.delete(id); deleted.push(id); + if (isPromptFileVariableEntry(item)) { + this._promptInstructions.remove(item); + } } } for (const item of upsert) { - - if (item.kind === 'promptFile') { - // TODO@jrieken @aeschli @legomushroom Let's make instructions normal - // attachment types so that this isn't needed - this.promptInstructions.add(item.value as URI); - continue; - } - const oldItem = this._attachments.get(item.id); if (!oldItem) { this._attachments.set(item.id, item); added.push(item); + if (isPromptFileVariableEntry(item)) { + this._promptInstructions.add([item]); + } } else if (!equals(oldItem, item)) { this._attachments.set(item.id, item); updated.push(item); diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentModel.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentModel.ts deleted file mode 100644 index c56f404de0f..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentModel.ts +++ /dev/null @@ -1,133 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../base/common/uri.js'; -import { pick } from '../../../../../base/common/arrays.js'; -import { Emitter } from '../../../../../base/common/event.js'; -import { PromptParser } from '../../common/promptSyntax/parsers/promptParser.js'; -import { BasePromptParser } from '../../common/promptSyntax/parsers/basePromptParser.js'; -import { ObservableDisposable } from '../../../../../base/common/observableDisposable.js'; -import { IPromptContentsProvider } from '../../common/promptSyntax/contentProviders/types.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; - -/** - * Type for a generic prompt parser object. - */ -type TPromptParser = BasePromptParser; - -/** - * Model for a single chat prompt instructions attachment. - */ -export class ChatPromptAttachmentModel extends ObservableDisposable { - /** - * Private reference of the underlying prompt instructions - * reference instance. - */ - private readonly _reference: TPromptParser; - - /** - * Get the prompt instructions reference instance. - */ - public get reference(): TPromptParser { - return this._reference; - } - - /** - * Get `URI` for the main reference and `URI`s of all valid child - * references it may contain, including reference of this model itself. - */ - public get references(): readonly URI[] { - const { reference } = this; - const { errorCondition } = reference; - - // return no references if the attachment is disabled - // or if this object itself has an error - if (errorCondition) { - return []; - } - - // otherwise return `URI` for the main reference and - // all valid child `URI` references it may contain - return [ - ...reference.allValidReferences.map(pick('uri')), - reference.uri, - ]; - } - - /** - * Get list of all tools associated with the prompt. - * - * Note! This property returns pont-in-time state of the tools metadata - * and does not take into account if the prompt or its nested child - * references are still being resolved. Please use the {@link settled} - * or {@link allSettled} properties if you need to retrieve the final - * list of the tools available. - */ - public get toolsMetadata(): readonly string[] | null { - return this.reference.allToolsMetadata; - } - - /** - * Promise that resolves when the prompt is fully parsed, - * including all its possible nested child references. - */ - public get allSettled(): Promise { - return this.reference.allSettled(); - } - - /** - * Get the top-level error of the prompt instructions - * reference, if any. - */ - public get topError() { - return this.reference.topError; - } - - /** - * Event that fires when the error condition of the prompt - * reference changes. - * - * See {@link onUpdate}. - */ - protected _onUpdate = this._register(new Emitter()); - /** - * Subscribe to the event that fires when the underlying prompt - * reference instance is updated. - * See {@link BasePromptParser.onUpdate}. - */ - public readonly onUpdate = this._onUpdate.event; - - constructor( - public readonly uri: URI, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(); - - this._reference = this._register( - this.instantiationService.createInstance( - PromptParser, - this.uri, - // in this case we know that the attached file must have been a - // prompt file, hence we pass the `allowNonPromptFiles` option - // to the provider to allow for non-prompt files to be attached - { allowNonPromptFiles: true }, - ) - ); - - this._register(this._reference.onUpdate( - this._onUpdate.fire.bind(this._onUpdate), - )); - } - - /** - * Start resolving the prompt instructions reference and child references - * that it may contain. - */ - public resolve(): this { - this._reference.start(); - - return this; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachments.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachments.ts new file mode 100644 index 00000000000..20e1bb26994 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachments.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancelablePromise, createCancelablePromise } from '../../../../../base/common/async.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../../base/common/map.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { IModelService } from '../../../../../editor/common/services/model.js'; +import { IChatRequestVariableEntry, IPromptFileVariableEntry, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js'; +import { IPromptParserResult, IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; + +/** + * Model for a collection of prompt instruction attachments. + * Starts + */ +export class ChatPromptAttachmentsCollection extends Disposable { + /** + * Event that fires then this model is updated. + * + * See {@linkcode onUpdate}. + */ + protected _onUpdate = this._register(new Emitter()); + + /** + * Subscribe to the `onUpdate` event. + */ + public onUpdate = this._onUpdate.event; + + /** + * List of all prompt instruction attachments. + */ + private _attachments = new ResourceMap>(); + + + constructor( + @ILanguageService private readonly languageService: ILanguageService, + @IModelService private readonly modelService: IModelService, + @IPromptsService private readonly promptsService: IPromptsService, + ) { + super(); + } + + /** + * Check if any of the attachments is a prompt file. + */ + public hasPromptFiles(promptFileLanguageId: string): boolean { + const hasLanguage = (uri: URI) => { + const model = this.modelService.getModel(uri); + const languageId = model ? model.getLanguageId() : this.languageService.guessLanguageIdByFilepathOrFirstLine(uri); + return languageId === promptFileLanguageId; + }; + + for (const uri of this._attachments.keys()) { + if (hasLanguage(uri)) { + return true; + } + } + return false; + } + + /** + * Get the list of all prompt instruction attachment variables, including all + * nested child references of each attachment explicitly attached by user. + */ + public async getAttachments(): Promise { + const result = []; + const attachments = [...this._attachments.values()]; + + for (const parseResultPromise of attachments) { + const parseResult = await parseResultPromise; + + // the usual URIs list of prompt instructions is `bottom-up`, therefore + // we do the same here - first add all child references of the model + for (const uri of parseResult.allValidReferences) { + result.push(toPromptFileVariableEntry(uri, false)); + } + + // then add the root reference of the model itself + result.push(toPromptFileVariableEntry(parseResult.uri, true)); + } + + return result; + } + + /** + * Add prompt instruction attachment instances + */ + public add(entries: IPromptFileVariableEntry[]) { + for (const entry of entries) { + const uri = entry.value; + + // if already exists, nothing to do + if (this._attachments.has(uri)) { + continue; + } + + const parseResult = createCancelablePromise(token => this.promptsService.parse(uri, token)); + parseResult.then(() => { + this._onUpdate.fire(entry); + }).catch((error) => { + // if parsing fails, we still create an attachment model + // to allow the user to see the error and fix it + console.error(`Failed to parse prompt file ${uri.toString()}:`, error); + }); + this._attachments.set(uri, parseResult); + } + } + + /** + * Remove a prompt instruction attachment instancs + */ + public remove(entry: IPromptFileVariableEntry): this { + const uri = entry.value; + + const attachment = this._attachments.get(uri); + if (attachment) { + this._attachments.delete(uri); + attachment.cancel(); + } + + return this; + } + + /** + * Clear all prompt instruction attachments. + */ + public clear(): this { + for (const attachment of this._attachments.values()) { + attachment.cancel(); + } + this._attachments.clear(); + + return this; + } + + public override dispose(): void { + super.dispose(); + this.clear(); // disposes of all attachments + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts deleted file mode 100644 index 94393439c16..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatPromptAttachmentsCollection.ts +++ /dev/null @@ -1,311 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../base/common/uri.js'; -import { Emitter } from '../../../../../base/common/event.js'; -import { basename, isEqual } from '../../../../../base/common/resources.js'; -import { ChatPromptAttachmentModel } from './chatPromptAttachmentModel.js'; -import { PromptsConfig } from '../../../../../platform/prompts/common/config.js'; -import { IPromptFileReference } from '../../common/promptSyntax/parsers/types.js'; -import { Disposable, DisposableMap } from '../../../../../base/common/lifecycle.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IChatRequestVariableEntry, IPromptVariableEntry, isChatRequestFileEntry } from '../../common/chatModel.js'; - -/** - * Prefix for all prompt instruction variable IDs. - */ -const PROMPT_VARIABLE_ID_PREFIX = 'vscode.prompt.instructions'; - -/** - * Prompt IDs start with a well-defined prefix that is used by - * the copilot extension to identify prompt references. - * - * @param uri The URI of the prompt file. - * @param isRoot Whether the prompt file is the root file, or a - * child reference that is nested inside the root file. - */ -export const createPromptVariableId = ( - uri: URI, - isRoot: boolean, -): string => { - // the default prefix that is used for all prompt files - let prefix = PROMPT_VARIABLE_ID_PREFIX; - // if the reference is the root object, add the `.root` suffix - if (isRoot) { - prefix += '.root'; - } - - // final `id` for all `prompt files` starts with the well-defined - // part that the copilot extension(or other chatbot) can rely on - return `${prefix}__${uri}`; -}; - -/** - * Utility to convert a {@link reference} to a chat variable entry. - * The `id` of the chat variable can be one of the following: - * - * - `vscode.prompt.instructions__`: for all non-root prompt file references - * - `vscode.prompt.instructions.root__`: for *root* prompt file references - * - ``: for the rest of references(the ones that do not point to a prompt file) - * - * @param reference A reference object to convert to a chat variable entry. - * @param isRoot If the reference is the root reference in the references tree. - * This object most likely was explicitly attached by the user. - */ -export const toChatVariable = ( - reference: Pick, - isRoot: boolean, -): IPromptVariableEntry => { - const { uri, isPromptFile } = reference; - - // default `id` is the stringified `URI` - let id = `${uri}`; - - // prompts have special `id`s that are used by the copilot extension - if (isPromptFile) { - id = createPromptVariableId(uri, isRoot); - } - - const name = (isPromptFile) - ? `prompt:${basename(uri)}` - : `file:${basename(uri)}`; - - const modelDescription = (isPromptFile) - ? 'Prompt instructions file' - : 'File attachment'; - - return { - id, - name, - value: uri, - kind: 'file', - modelDescription, - isRoot, - }; -}; - -/** - * Checks of a provided chat variable is a `prompt file` variable. - */ -export function isPromptFileChatVariable( - variable: IChatRequestVariableEntry, -): variable is IPromptVariableEntry { - return isChatRequestFileEntry(variable) - && variable.id.startsWith(PROMPT_VARIABLE_ID_PREFIX); -} - -/** - * Adds the provided `newReference` to the list of chat variables if it is not already present. - */ -export function addPromptFileChatVariable(variables: IChatRequestVariableEntry[], newReference: URI): void { - if (!variables.some(variable => isPromptFileChatVariable(variable) && isEqual(IChatRequestVariableEntry.toUri(variable), newReference))) { - variables.push(toChatVariable({ uri: newReference, isPromptFile: true }, true)); - } -} - -/** - * Model for a collection of prompt instruction attachments. - * See {@linkcode ChatPromptAttachmentModel} for individual attachment. - */ -export class ChatPromptAttachmentsCollection extends Disposable { - /** - * Event that fires then this model is updated. - * - * See {@linkcode onUpdate}. - */ - protected _onUpdate = this._register(new Emitter()); - /** - * Subscribe to the `onUpdate` event. - */ - public onUpdate = this._onUpdate.event; - - /** - * Event that fires when a new prompt instruction attachment is added. - * See {@linkcode onAdd}. - */ - protected _onAdd = this._register(new Emitter()); - /** - * The `onAdd` event fires when a new prompt instruction attachment is added. - */ - public onAdd = this._onAdd.event; - - /** - * Event that fires when a new prompt instruction attachment is removed. - * See {@linkcode onRemove}. - */ - protected _onRemove = this._register(new Emitter()); - /** - * The `onRemove` event fires when a new prompt instruction attachment is removed. - */ - public onRemove = this._onRemove.event; - - /** - * List of all prompt instruction attachments. - */ - private attachments: DisposableMap = - this._register(new DisposableMap()); - - /** - * Get all `URI`s of all valid references, including all - * the possible references nested inside the children. - */ - public get references(): readonly URI[] { - const result = []; - - for (const child of this.attachments.values()) { - result.push(...child.references); - } - - return result; - } - - /** - * Get list of tools associated with all attached prompt files. - */ - public get toolsMetadata(): readonly string[] | null { - const result = []; - - for (const child of this.attachments.values()) { - const { toolsMetadata } = child; - - if (toolsMetadata === null) { - continue; - } - - result.push(...toolsMetadata); - } - - // return unique list of all tools - return [...new Set(result)]; - } - - /** - * Get the list of all prompt instruction attachment variables, including all - * nested child references of each attachment explicitly attached by user. - */ - public get chatAttachments(): readonly IChatRequestVariableEntry[] { - const result = []; - const attachments = [...this.attachments.values()]; - - for (const attachment of attachments) { - const { reference } = attachment; - - // the usual URIs list of prompt instructions is `bottom-up`, therefore - // we do the same here - first add all child references of the model - result.push( - ...reference.allValidReferences.map((link) => { - return toChatVariable(link, false); - }), - ); - - // then add the root reference of the model itself - result.push( - toChatVariable({ - uri: reference.uri, - // the attached file must have been a prompt file therefore - // we force that assumption here; this makes sure that prompts - // in untitled documents can be also attached to the chat input - isPromptFile: true, - }, true), - ); - } - - return result; - } - - /** - * Promise that resolves when parsing of all attached prompt instruction - * files completes, including parsing of all its possible child references. - */ - public async allSettled(): Promise { - const attachments = [...this.attachments.values()]; - - await Promise.allSettled( - attachments.map((attachment) => { - return attachment.allSettled; - }), - ); - - return this; - } - - constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configService: IConfigurationService, - ) { - super(); - } - - /** - * Add a prompt instruction attachment instance with the provided `URI`. - * @param uri URI of the prompt instruction attachment to add. - */ - public add(uris: URI | readonly URI[]) { - const uriList = Array.isArray(uris) ? uris : [uris]; - - // if no URIs provided, nothing to do - if (uriList.length === 0) { - return; - } - - for (const uri of uriList) { - // if already exists, nothing to do - if (this.attachments.has(uri.path)) { - continue; - } - - const instruction = this.instantiationService.createInstance(ChatPromptAttachmentModel, uri); - instruction.addDisposables( - instruction.onDispose(() => { - // note! we have to use `deleteAndLeak` here, because the `*AndDispose` - // alternative results in an infinite loop of calling this callback - this.attachments.deleteAndLeak(uri.path); - this._onUpdate.fire(); - this._onRemove.fire(instruction); - }), - instruction.onUpdate(this._onUpdate.fire), - ); - - this.attachments.set(uri.path, instruction); - this._onAdd.fire(instruction); - this._onUpdate.fire(); - } - } - - /** - * Remove a prompt instruction attachment instance by provided `URI`. - * @param uri URI of the prompt instruction attachment to remove. - */ - public remove(uri: URI): this { - // if does not exist, nothing to do - if (!this.attachments.has(uri.path)) { - return this; - } - - this.attachments.deleteAndDispose(uri.path); - - return this; - } - - /** - * Checks if the prompt instructions feature is enabled in the user settings. - */ - public get featureEnabled(): boolean { - return PromptsConfig.enabled(this.configService); - } - - /** - * Clear all prompt instruction attachments. - */ - public clear(): this { - for (const attachment of this.attachments.values()) { - this.remove(attachment.uri); - } - - this._onUpdate.fire(); - return this; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts index 87338b0e95c..ff3de42cd29 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentResolve.ts @@ -24,7 +24,9 @@ import { UntitledTextEditorInput } from '../../../services/untitled/common/untit import { createNotebookOutputVariableEntry, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT_CONST } from '../../notebook/browser/contrib/chat/notebookChatUtils.js'; import { getOutputViewModelFromId } from '../../notebook/browser/controller/cellOutputActions.js'; import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js'; -import { CHAT_ATTACHABLE_IMAGE_MIME_TYPES, getAttachableImageExtension, IChatRequestVariableEntry, IDiagnosticVariableEntry, IDiagnosticVariableEntryFilterData, ISymbolVariableEntry, OmittedState } from '../common/chatModel.js'; +import { CHAT_ATTACHABLE_IMAGE_MIME_TYPES, getAttachableImageExtension } from '../common/chatModel.js'; +import { IChatRequestVariableEntry, OmittedState, IDiagnosticVariableEntry, IDiagnosticVariableEntryFilterData, ISymbolVariableEntry, toPromptFileVariableEntry } from '../common/chatVariableEntries.js'; +import { getPromptsTypeForLanguageId } from '../common/promptSyntax/promptTypes.js'; import { imageToHash } from './chatPasteProviders.js'; import { resizeImage } from './imageUtils.js'; @@ -82,8 +84,11 @@ export async function resolveResourceAttachContext(resource: URI, isDirectory: b let omittedState = OmittedState.NotOmitted; if (!isDirectory) { + + let languageId: string | undefined; try { const createdModel = await textModelService.createModelReference(resource); + languageId = createdModel.object.getLanguageId(); createdModel.dispose(); } catch { omittedState = OmittedState.Full; @@ -92,6 +97,9 @@ export async function resolveResourceAttachContext(resource: URI, isDirectory: b if (/\.(svg)$/i.test(resource.path)) { omittedState = OmittedState.Full; } + if (languageId && getPromptsTypeForLanguageId(languageId)) { + return toPromptFileVariableEntry(resource, true); + } } return { diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 2eb7abd3f61..ffda839a0a2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -25,12 +25,11 @@ import { IOpenerService, OpenInternalOptions } from '../../../../platform/opener import { IThemeService, FolderThemeIcon } from '../../../../platform/theme/common/themeService.js'; import { IResourceLabel, ResourceLabels, IFileLabelOptions } from '../../../browser/labels.js'; import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js'; -import { IChatRequestPasteVariableEntry, IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, ISCMHistoryItemVariableEntry, OmittedState } from '../common/chatModel.js'; +import { IChatRequestPasteVariableEntry, IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, ISCMHistoryItemVariableEntry, OmittedState } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; -import { chatAttachmentResourceContextKey } from './chatContentParts/chatAttachmentsContentPart.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { basename, dirname } from '../../../../base/common/path.js'; -import { IContextKey, IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; @@ -54,6 +53,7 @@ import { IChatContentReference } from '../common/chatService.js'; import { getHistoryItemEditorTitle, getHistoryItemHoverContent } from '../../scm/browser/util.js'; import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js'; import { Iterable } from '../../../../base/common/iterator.js'; +import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; abstract class AbstractChatAttachmentWidget extends Disposable { public readonly element: HTMLElement; @@ -475,6 +475,91 @@ export class DefaultChatAttachmentWidget extends AbstractChatAttachmentWidget { } } +export class PromptFileAttachmentWidget extends AbstractChatAttachmentWidget { + + private hintElement: HTMLElement; + + constructor( + resource: URI, + attachment: IChatRequestVariableEntry, + currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, + contextResourceLabels: ResourceLabels, + hoverDelegate: IHoverDelegate, + @ICommandService commandService: ICommandService, + @IOpenerService openerService: IOpenerService, + @ILabelService private readonly labelService: ILabelService, + @ILanguageService private readonly languageService: ILanguageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + + + this.hintElement = dom.append(this.element, dom.$('span.prompt-type')); + + this.updateLabel(resource); + + this.instantiationService.invokeFunction(accessor => { + this._register(hookUpResourceAttachmentDragAndContextMenu(accessor, this.element, resource)); + }); + this.addResourceOpenHandlers(resource, undefined); + + this.attachClearButton(); + } + + private updateLabel(resource: URI) { + const fileBasename = basename(resource.path); + const fileDirname = dirname(resource.path); + const friendlyName = `${fileBasename} ${fileDirname}`; + const isPrompt = this.languageService.guessLanguageIdByFilepathOrFirstLine(resource) === 'prompt'; + const ariaLabel = isPrompt + ? localize('chat.promptAttachment', "Prompt file, {0}", friendlyName) + : localize('chat.instructionsAttachment', "Instructions attachment, {0}", friendlyName); + const typeLabel = isPrompt + ? localize('prompt', "Prompt") + : localize('instructions', "Instructions"); + + const uriLabel = this.labelService.getUriLabel(resource, { relative: true }); + const title = `${typeLabel} ${uriLabel}`; + + //const { topError } = this.promptFile; + this.element.classList.remove('warning', 'error'); + + // if there are some errors/warning during the process of resolving + // attachment references (including all the nested child references), + // add the issue details in the hover title for the attachment, one + // error/warning at a time because there is a limited space available + // if (topError) { + // const { errorSubject: subject } = topError; + // const isError = (subject === 'root'); + // this.element.classList.add((isError) ? 'error' : 'warning'); + + // const severity = (isError) + // ? localize('error', "Error") + // : localize('warning', "Warning"); + + // title += `\n[${severity}]: ${topError.localizedMessage}`; + // } + + const fileWithoutExtension = getCleanPromptName(resource); + this.label.setFile(URI.file(fileWithoutExtension), { + fileKind: FileKind.FILE, + hidePath: true, + range: undefined, + title, + icon: ThemeIcon.fromId(Codicon.bookmark.id), + extraClasses: [], + }); + + this.hintElement.innerText = typeLabel; + + + this.element.ariaLabel = ariaLabel; + } +} + + export class ToolSetOrToolItemAttachmentWidget extends AbstractChatAttachmentWidget { constructor( attachment: IChatRequestToolSetEntry | IChatRequestToolEntry, @@ -815,3 +900,5 @@ function addBasicContextMenu(accessor: ServicesAccessor, widget: HTMLElement, sc }); }); } + +export const chatAttachmentResourceContextKey = new RawContextKey('chatAttachmentResource', undefined, { type: 'URI', description: localize('resource', "The full value of the chat attachment resource, including scheme and path") }); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index ff5fcc3eda6..17f3494cd2f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -10,16 +10,11 @@ import { Disposable, DisposableStore } from '../../../../../base/common/lifecycl import { basename } from '../../../../../base/common/path.js'; import { URI } from '../../../../../base/common/uri.js'; import { Range } from '../../../../../editor/common/core/range.js'; -import { localize } from '../../../../../nls.js'; -import { RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ResourceLabels } from '../../../../browser/labels.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; - -export const chatAttachmentResourceContextKey = new RawContextKey('chatAttachmentResource', undefined, { type: 'URI', description: localize('resource', "The full value of the chat attachment resource, including scheme and path") }); - +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; export class ChatAttachmentsContentPart extends Disposable { private readonly attachedContextDisposables = this._register(new DisposableStore()); @@ -64,6 +59,8 @@ export class ChatAttachmentsContentPart extends Disposable { } else if (isImageVariableEntry(attachment)) { attachment.omittedState = isAttachmentPartialOrOmitted ? OmittedState.Full : attachment.omittedState; widget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + } else if (resource && isPromptFileVariableEntry(attachment)) { + widget = this.instantiationService.createInstance(PromptFileAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) { widget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } else if (isPasteVariableEntry(attachment)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts index 976b3b74674..d0c6a600bad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { $ } from '../../../../../base/browser/dom.js'; import { ButtonWithIcon } from '../../../../../base/browser/ui/button/button.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; @@ -13,7 +14,6 @@ import { localize } from '../../../../../nls.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { ChatTreeItem } from '../chat.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; -import { $ } from './chatReferencesContentPart.js'; export abstract class ChatCollapsibleContentPart extends Disposable implements IChatContentPart { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index e5dd645e80b..061a92a0154 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -176,7 +176,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { protected renderMessage(element: HTMLElement, listContainer: HTMLElement): void { this.messageElement.append(element); - if (this._configurationService.getValue('chat.focusWindowOnConfirmation')) { + if (this._configurationService.getValue('chat.notifyWindowOnConfirmation')) { const targetWindow = dom.getWindow(listContainer); if (!targetWindow.document.hasFocus()) { this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts index 282f80349a8..c546cc5c2b3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -5,6 +5,7 @@ import * as dom from '../../../../../base/browser/dom.js'; import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; +import { IListOptions } from '../../../../../base/browser/ui/list/listWidget.js'; import { coalesce } from '../../../../../base/common/arrays.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Event } from '../../../../../base/common/event.js'; @@ -46,7 +47,7 @@ import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js'; import { IDisposableReference, ResourcePool } from './chatCollections.js'; import { IChatContentPartRenderContext } from './chatContentParts.js'; -export const $ = dom.$; +const $ = dom.$; export interface IChatReferenceListItem extends IChatContentReference { title?: string; @@ -184,6 +185,7 @@ export class CollapsibleListPool extends Disposable { constructor( private _onDidChangeVisibility: Event, private readonly menuId: MenuId | undefined, + private readonly listOptions: IListOptions | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @@ -205,6 +207,7 @@ export class CollapsibleListPool extends Disposable { new CollapsibleListDelegate(), [this.instantiationService.createInstance(CollapsibleListRenderer, resourceLabels, this.menuId)], { + ...this.listOptions, alwaysConsumeMouseWheel: false, accessibilityProvider: { getAriaLabel: (element: IChatCollapsibleListItem) => { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts index ffc765e7940..873796c8a59 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts @@ -31,7 +31,8 @@ import { INotificationService } from '../../../../../platform/notification/commo import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../../files/browser/fileConstants.js'; -import { getAttachableImageExtension, IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { getAttachableImageExtension } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { ChatTreeItem, IChatCodeBlockInfo } from '../chat.js'; import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions } from '../codeBlockPart.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index 85170e72008..da36386ccaa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -118,7 +118,6 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { reserveWidth: 19, verticalPadding: 5, editorOptions: { - wordWrap: 'on', tabFocusMode: true, ariaLabel: typeof title === 'string' ? title : title.value }, diff --git a/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts b/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts index 9220bee6f48..9da828eaa3f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts @@ -10,7 +10,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { isObject } from '../../../../base/common/types.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; -import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatWidget } from './chat.js'; @@ -43,18 +43,25 @@ export interface IChatContextValueItem extends IChatContextItem { export type ChatContextPick = IChatContextPickerPickItem | IQuickPickSeparator; +export interface IChatContextPicker { + readonly placeholder: string; + /** + * Picks that should either be: + * - A promise that resolves to the picked items + * - A function that maps input query into items to display. + */ + readonly picks: Promise | ((query: IObservable, token: CancellationToken) => IObservable<{ busy: boolean; picks: ChatContextPick[] }>); + + readonly configure?: { + label: string; + commandId: string; + }; +} + export interface IChatContextPickerItem extends IChatContextItem { readonly type: 'pickerPick'; - asPicker(widget: IChatWidget): { - readonly placeholder: string; - /** - * Picks that should either be: - * - A promise that resolves to the picked items - * - A function that maps input query into items to display. - */ - readonly picks: Promise | ((query: IObservable, token: CancellationToken) => IObservable<{ busy: boolean; picks: ChatContextPick[] }>); - }; + asPicker(widget: IChatWidget): IChatContextPicker; } /** diff --git a/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts b/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts index e3f054946e5..bd7d0d9a249 100644 --- a/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts +++ b/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts @@ -23,7 +23,7 @@ import { IThemeService, Themable } from '../../../../platform/theme/common/theme import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; -import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatWidgetService } from './chat.js'; import { ImageTransferData, resolveEditorAttachContext, resolveImageAttachContext, resolveMarkerAttachContext, resolveNotebookOutputAttachContext, resolveSymbolsAttachContext } from './chatAttachmentResolve.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts b/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts index cbc084b3b82..d114f65a86f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ITextModel } from '../../../../editor/common/model.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js'; -import { ChatInputPart } from './chatInputPart.js'; export class ChatInputBoxContentProvider extends Disposable implements ITextModelContentProvider { @@ -19,7 +19,7 @@ export class ChatInputBoxContentProvider extends Disposable implements ITextMode @ILanguageService private readonly languageService: ILanguageService ) { super(); - this._register(textModelService.registerTextModelContentProvider(ChatInputPart.INPUT_SCHEME, this)); + this._register(textModelService.registerTextModelContentProvider(Schemas.vscodeChatInput, this)); } async provideTextContent(resource: URI): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index f50b6b77b57..c7dcae66c15 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -101,58 +101,6 @@ abstract class WorkingSetAction extends EditingSessionAction { abstract runWorkingSetAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget | undefined, ...uris: URI[]): any; } -registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction { - constructor() { - super({ - id: 'chatEditing.removeFileFromWorkingSet', - title: localize2('removeFileFromWorkingSet', 'Remove File'), - icon: Codicon.close, - precondition: ChatContextKeys.requestInProgress.negate(), - menu: [{ - id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - // when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)), - order: 5, - group: 'navigation' - }], - }); - } - - async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise { - const dialogService = accessor.get(IDialogService); - - const pendingEntries = currentEditingSession.entries.get().filter((entry) => uris.includes(entry.modifiedURI) && entry.state.get() === ModifiedFileEntryState.Modified); - if (pendingEntries.length > 0) { - // Ask for confirmation if there are any pending edits - const file = pendingEntries.length > 1 - ? localize('chat.editing.removeFile.confirmationmanyFiles', "{0} files", pendingEntries.length) - : basename(pendingEntries[0].modifiedURI); - const confirmation = await dialogService.confirm({ - title: localize('chat.editing.removeFile.confirmation.title', "Remove {0} from working set?", file), - message: localize('chat.editing.removeFile.confirmation.message', "This will remove {0} from your working set and undo the edits made to it. Do you want to proceed?", file), - primaryButton: localize('chat.editing.removeFile.confirmation.primaryButton', "Yes"), - type: 'info' - }); - if (!confirmation.confirmed) { - return; - } - } - - // Remove from working set - await currentEditingSession.reject(...uris); - currentEditingSession.remove(...uris); - - // Remove from chat input part - for (const uri of uris) { - chatWidget.attachmentModel.delete(uri.toString()); - } - - // Clear all related file suggestions - if (chatWidget.attachmentModel.fileAttachments.length === 0) { - chatWidget.input.relatedFiles?.clear(); - } - } -}); - registerAction2(class OpenFileInDiffAction extends WorkingSetAction { constructor() { super({ @@ -328,41 +276,6 @@ export async function discardAllEditsWithConfirmation(accessor: ServicesAccessor return true; } -// TODO@roblourens this may be obsolete? -export class ChatEditingRemoveAllFilesAction extends EditingSessionAction { - static readonly ID = 'chatEditing.clearWorkingSet'; - - constructor() { - super({ - id: ChatEditingRemoveAllFilesAction.ID, - title: localize('clearWorkingSet', 'Clear Working Set'), - icon: Codicon.clearAll, - tooltip: localize('clearWorkingSet', 'Clear Working Set'), - precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate()), - menu: [ - { - id: MenuId.ChatEditingWidgetToolbar, - group: 'navigation', - order: 5, - when: hasAppliedChatEditsContextKey.negate() - } - ] - }); - } - - override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { - // Remove all files from working set - const uris = [...editingSession.entries.get()].map((e) => e.modifiedURI); - editingSession.remove(...uris); - - // Remove all file attachments - const fileAttachments = chatWidget.attachmentModel ? chatWidget.attachmentModel.fileAttachments : []; - const attachmentIdsToRemove = fileAttachments.map(attachment => attachment.toString()); - chatWidget.attachmentModel.delete(...attachmentIdsToRemove); - } -} -registerAction2(ChatEditingRemoveAllFilesAction); - export class ChatEditingShowChangesAction extends EditingSessionAction { static readonly ID = 'chatEditing.viewChanges'; static readonly LABEL = localize('chatEditing.viewChanges', 'View All Edits'); @@ -399,7 +312,7 @@ registerAction2(class RemoveAction extends Action2 { title: localize2('chat.undoEdits.label', "Undo Requests"), f1: false, category: CHAT_CATEGORY, - icon: Codicon.x, + icon: Codicon.discard, keybinding: { primary: KeyCode.Delete, mac: { @@ -421,9 +334,9 @@ registerAction2(class RemoveAction extends Action2 { async run(accessor: ServicesAccessor, ...args: any[]) { let item: ChatTreeItem | undefined = args[0]; + const chatWidgetService = accessor.get(IChatWidgetService); + const widget = chatWidgetService.lastFocusedWidget; if (!isResponseVM(item) && !isRequestVM(item)) { - const chatWidgetService = accessor.get(IChatWidgetService); - const widget = chatWidgetService.lastFocusedWidget; item = widget?.getFocus(); } @@ -496,6 +409,11 @@ registerAction2(class RemoveAction extends Action2 { const snapshotRequestId = chatRequests[itemIndex].id; await session.restoreSnapshot(snapshotRequestId, undefined); } + + if (isRequestVM(item) && configurationService.getValue('chat.undoRequests.restoreInput')) { + widget?.focusInput(); + widget?.input.setValue(item.messageText, false); + } } }); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts index 031262ee01d..f8d12d6a5a4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts @@ -339,7 +339,7 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito // Note, this is a workaround for the `LineRange.isEmpty()` in diffEntry.original being `false` for newly inserted content const isCreatedContent = decorations.length === 1 && decorations[0].range.isEmpty() && diffEntry.original.startLineNumber === 1; - if (!diffEntry.modified.isEmpty && !(isCreatedContent && (diffEntry.modified.endLineNumberExclusive - 1) === editorLineCount)) { + if (!diffEntry.modified.isEmpty) { modifiedVisualDecorations.push({ range: diffEntry.modified.toInclusiveRange()!, options: chatDiffWholeLineAddDecoration diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 629cbc061f5..85d997a3c35 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -251,12 +251,20 @@ abstract class AcceptRejectHunkAction extends ChatEditingEditorAction { ); } - override async runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, _entry: IModifiedFileEntry, ctrl: IModifiedFileEntryEditorIntegration, ...args: any[]): Promise { + override async runChatEditingCommand(accessor: ServicesAccessor, session: IChatEditingSession, entry: IModifiedFileEntry, ctrl: IModifiedFileEntryEditorIntegration, ...args: any[]): Promise { + + const instaService = accessor.get(IInstantiationService); + if (this._accept) { await ctrl.acceptNearestChange(args[0]); } else { await ctrl.rejectNearestChange(args[0]); } + + if (entry.changesCount.get() === 0) { + // no more changes, move to next file + await instaService.invokeFunction(openNextOrPreviousChange, session, entry, true); + } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 1c7566e986a..7526326abc2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -3,85 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener, getWindow } from '../../../../../base/browser/dom.js'; -import { assert } from '../../../../../base/common/assert.js'; -import { DeferredPromise, RunOnceScheduler, timeout } from '../../../../../base/common/async.js'; -import { IReference, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; -import { ITransaction, autorun, observableValue, transaction } from '../../../../../base/common/observable.js'; -import { isEqual } from '../../../../../base/common/resources.js'; -import { themeColorFromId } from '../../../../../base/common/themables.js'; +import { IReference, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { ITransaction, autorun, transaction } from '../../../../../base/common/observable.js'; import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; -import { EditOperation, ISingleEditOperation } from '../../../../../editor/common/core/editOperation.js'; -import { StringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; -import { Range } from '../../../../../editor/common/core/range.js'; -import { IDocumentDiff, nullDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; -import { DetailedLineRangeMapping } from '../../../../../editor/common/diff/rangeMapping.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; -import { IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane } from '../../../../../editor/common/model.js'; +import { ITextModel } from '../../../../../editor/common/model.js'; import { SingleModelEditStackElement } from '../../../../../editor/common/model/editStack.js'; -import { ModelDecorationOptions, createTextBufferFactoryFromSnapshot } from '../../../../../editor/common/model/textModel.js'; -import { offsetEditFromContentChanges, offsetEditFromLineRangeMapping, offsetEditToEditOperations } from '../../../../../editor/common/model/textModelStringEdit.js'; -import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; +import { createTextBufferFactoryFromSnapshot } from '../../../../../editor/common/model/textModel.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { TextModelEditReason } from '../../../../../editor/common/textModelEditReason.js'; -import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; import { localize } from '../../../../../nls.js'; -import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IMarkerService } from '../../../../../platform/markers/common/markers.js'; -import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js'; import { IUndoRedoElement, IUndoRedoService } from '../../../../../platform/undoRedo/common/undoRedo.js'; import { IEditorPane, SaveReason } from '../../../../common/editor.js'; import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { ITextFileService, isTextFileEditorModel, stringToSnapshot } from '../../../../services/textfile/common/textfiles.js'; import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; -import { ChatEditKind, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; -import { ChatEditingCodeEditorIntegration, IDocumentDiff2 } from './chatEditingCodeEditorIntegration.js'; -import { AbstractChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry, pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js'; +import { ChatEditingCodeEditorIntegration } from './chatEditingCodeEditorIntegration.js'; +import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; +import { ChatEditingTextModelChangeService } from './chatEditingTextModelChangeService.js'; import { ChatEditingSnapshotTextModelContentProvider, ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifiedFileEntry implements IModifiedFileEntry { - private static readonly _lastEditDecorationOptions = ModelDecorationOptions.register({ - isWholeLine: true, - description: 'chat-last-edit', - className: 'chat-editing-last-edit-line', - marginClassName: 'chat-editing-last-edit', - overviewRuler: { - position: OverviewRulerLane.Full, - color: themeColorFromId(editorSelectionBackground) - }, - }); - - private static readonly _pendingEditDecorationOptions = ModelDecorationOptions.register({ - isWholeLine: true, - description: 'chat-pending-edit', - className: 'chat-editing-pending-edit', - minimap: { - position: MinimapPosition.Inline, - color: themeColorFromId(pendingRewriteMinimap) - } - }); - - private static readonly _atomicEditDecorationOptions = ModelDecorationOptions.register({ - isWholeLine: true, - description: 'chat-atomic-edit', - className: 'chat-editing-atomic-edit', - minimap: { - position: MinimapPosition.Inline, - color: themeColorFromId(pendingRewriteMinimap) - } - }); - readonly initialContent: string; private readonly originalModel: ITextModel; @@ -89,20 +43,12 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie private readonly _docFileEditorModel: IResolvedTextEditorModel; - private _edit: StringEdit = StringEdit.empty; - private _isEditFromUs: boolean = false; - private _allEditsAreFromUs: boolean = true; - private _diffOperation: Promise | undefined; - private _diffOperationIds: number = 0; - - private readonly _diffInfo = observableValue(this, nullDocumentDiff); - - readonly changesCount = this._diffInfo.map(diff => diff.changes.length); - - private readonly _editDecorationClear = this._register(new RunOnceScheduler(() => { this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, []); }, 500)); - private _editDecorations: string[] = []; + override get changesCount() { + return this._textModelChangeService.diffInfo.map(diff => diff.changes.length); + } readonly originalURI: URI; + private readonly _textModelChangeService: ChatEditingTextModelChangeService; constructor( resourceRef: IReference, @@ -117,12 +63,10 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie @IConfigurationService configService: IConfigurationService, @IFilesConfigurationService fileConfigService: IFilesConfigurationService, @IChatService chatService: IChatService, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ITextFileService private readonly _textFileService: ITextFileService, @IFileService fileService: IFileService, @IUndoRedoService undoRedoService: IUndoRedoService, - @IInstantiationService instantiationService: IInstantiationService, - @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, + @IInstantiationService instantiationService: IInstantiationService ) { super( resourceRef.object.textEditorModel.uri, @@ -150,6 +94,14 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie ) ); + this._textModelChangeService = this._register(instantiationService.createInstance(ChatEditingTextModelChangeService, + this.originalModel, this.modifiedModel, this._stateObs)); + + this._register(this._textModelChangeService.onDidAcceptOrRejectAllHunks(action => { + this._stateObs.set(action, undefined); + this._notifyAction(action === ModifiedFileEntryState.Accepted ? 'accepted' : 'rejected'); + })); + // Create a reference to this model to avoid it being disposed from under our nose (async () => { const reference = await textModelService.createModelReference(docSnapshot.uri); @@ -161,10 +113,12 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie })(); - this._register(this.modifiedModel.onDidChangeContent(e => this._mirrorEdits(e))); - - this._register(toDisposable(() => { - this._clearCurrentEditLineDecoration(); + this._register(this._textModelChangeService.onDidUserEditModel(() => { + this._userEditScheduler.schedule(); + const didResetToOriginalContent = this.modifiedModel.getValue() === this.initialContent; + if (this._stateObs.get() === ModifiedFileEntryState.Modified && didResetToOriginalContent) { + this._stateObs.set(ModifiedFileEntryState.Rejected, undefined); + } })); const resourceFilter = this._register(new MutableDisposable()); @@ -180,17 +134,12 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie })); } - private _clearCurrentEditLineDecoration() { - this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, []); - } - equalsSnapshot(snapshot: ISnapshotEntry | undefined): boolean { return !!snapshot && this.modifiedURI.toString() === snapshot.resource.toString() && this.modifiedModel.getLanguageId() === snapshot.languageId && this.originalModel.getValue() === snapshot.original && this.modifiedModel.getValue() === snapshot.current && - this._edit.equals(snapshot.originalToCurrentEdit) && this.state.get() === snapshot.state; } @@ -201,88 +150,27 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie snapshotUri: ChatEditingSnapshotTextModelContentProvider.getSnapshotFileURI(this._telemetryInfo.sessionId, requestId, undoStop, this.modifiedURI.path), original: this.originalModel.getValue(), current: this.modifiedModel.getValue(), - originalToCurrentEdit: this._edit, state: this.state.get(), telemetryInfo: this._telemetryInfo }; } - restoreFromSnapshot(snapshot: ISnapshotEntry, restoreToDisk = true) { + async restoreFromSnapshot(snapshot: ISnapshotEntry, restoreToDisk = true) { this._stateObs.set(snapshot.state, undefined); - this.originalModel.setValue(snapshot.original); - if (restoreToDisk) { - this._setDocValue(snapshot.current); - } - this._edit = snapshot.originalToCurrentEdit; - this._updateDiffInfoSeq(); + await this._textModelChangeService.resetDocumentValues(snapshot.original, restoreToDisk ? snapshot.current : undefined); } - resetToInitialContent() { - this._setDocValue(this.initialContent); + async resetToInitialContent() { + await this._textModelChangeService.resetDocumentValues(undefined, this.initialContent); } protected override async _areOriginalAndModifiedIdentical(): Promise { - const diff = await this._diffOperation; - return diff ? diff.identical : false; + return this._textModelChangeService.areOriginalAndModifiedIdentical(); } protected override _resetEditsState(tx: ITransaction): void { super._resetEditsState(tx); - this._clearCurrentEditLineDecoration(); - } - - private _mirrorEdits(event: IModelContentChangedEvent) { - const edit = offsetEditFromContentChanges(event.changes); - - if (this._isEditFromUs) { - const e_sum = this._edit; - const e_ai = edit; - this._edit = e_sum.compose(e_ai); - } else { - - // e_ai - // d0 ---------------> s0 - // | | - // | | - // | e_user_r | e_user - // | | - // | | - // v e_ai_r v - /// d1 ---------------> s1 - // - // d0 - document snapshot - // s0 - document - // e_ai - ai edits - // e_user - user edits - // - const e_ai = this._edit; - const e_user = edit; - - const e_user_r = e_user.tryRebase(e_ai.inverse(this.originalModel.getValue()), true); - - if (e_user_r === undefined) { - // user edits overlaps/conflicts with AI edits - this._edit = e_ai.compose(e_user); - } else { - const edits = offsetEditToEditOperations(e_user_r, this.originalModel); - this.originalModel.applyEdits(edits); - this._edit = e_ai.tryRebase(e_user_r); - } - - this._allEditsAreFromUs = false; - this._userEditScheduler.schedule(); - this._updateDiffInfoSeq(); - - const didResetToOriginalContent = this.modifiedModel.getValue() === this.initialContent; - const currentState = this._stateObs.get(); - switch (currentState) { - case ModifiedFileEntryState.Modified: - if (didResetToOriginalContent) { - this._stateObs.set(ModifiedFileEntryState.Rejected, undefined); - break; - } - } - } + this._textModelChangeService.clearCurrentEditLineDecoration(); } protected override _createUndoRedoElement(response: IChatResponseModel): IUndoRedoElement { @@ -293,75 +181,7 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie async acceptAgentEdits(resource: URI, textEdits: (TextEdit | ICellEditOperation)[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise { - assertType(textEdits.every(TextEdit.isTextEdit), 'INVALID args, can only handle text edits'); - assert(isEqual(resource, this.modifiedURI), ' INVALID args, can only edit THIS document'); - - const isAtomicEdits = textEdits.length > 0 && isLastEdits; - - let rewriteRatio = 0; - - if (isAtomicEdits) { - // EDIT and DONE - const minimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this.modifiedModel.uri, textEdits) ?? textEdits; - const ops = minimalEdits.map(TextEdit.asEditOperation); - const undoEdits = this._applyEdits(ops); - - if (undoEdits.length > 0) { - let range: Range | undefined; - for (let i = 0; i < undoEdits.length; i++) { - const op = undoEdits[i]; - if (!range) { - range = Range.lift(op.range); - } else { - range = Range.plusRange(range, op.range); - } - } - if (range) { - - const defer = new DeferredPromise(); - const listener = addDisposableListener(getWindow(undefined), 'animationend', e => { - if (e.animationName === 'kf-chat-editing-atomic-edit') { // CHECK with chat.css - defer.complete(); - listener.dispose(); - } - }); - - this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, [{ - options: ChatEditingModifiedDocumentEntry._atomicEditDecorationOptions, - range - }]); - - await Promise.any([defer.p, timeout(500)]); // wait for animation to finish but also time-cap it - listener.dispose(); - } - } - - - } else { - // EDIT a bit, then DONE - const ops = textEdits.map(TextEdit.asEditOperation); - const undoEdits = this._applyEdits(ops); - const maxLineNumber = undoEdits.reduce((max, op) => Math.max(max, op.range.startLineNumber), 0); - rewriteRatio = Math.min(1, maxLineNumber / this.modifiedModel.getLineCount()); - - const newDecorations: IModelDeltaDecoration[] = [ - // decorate pending edit (region) - { - options: ChatEditingModifiedDocumentEntry._pendingEditDecorationOptions, - range: new Range(maxLineNumber + 1, 1, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) - } - ]; - - if (maxLineNumber > 0) { - // decorate last edit - newDecorations.push({ - options: ChatEditingModifiedDocumentEntry._lastEditDecorationOptions, - range: new Range(maxLineNumber, 1, maxLineNumber, Number.MAX_SAFE_INTEGER) - }); - } - this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, newDecorations); - - } + const result = await this._textModelChangeService.acceptAgentEdits(resource, textEdits, isLastEdits); transaction((tx) => { this._waitsForLastEdits.set(!isLastEdits, tx); @@ -369,13 +189,10 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie if (!isLastEdits) { this._isCurrentlyBeingModifiedByObs.set(responseModel, tx); - this._rewriteRatioObs.set(rewriteRatio, tx); - + this._rewriteRatioObs.set(result.rewriteRatio, tx); } else { this._resetEditsState(tx); - this._updateDiffInfoSeq(); this._rewriteRatioObs.set(1, tx); - this._editDecorationClear.schedule(); } }); if (isLastEdits) { @@ -386,116 +203,10 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie } } - private async _acceptHunk(change: DetailedLineRangeMapping): Promise { - if (!this._diffInfo.get().changes.includes(change)) { - // diffInfo should have model version ids and check them (instead of the caller doing that) - return false; - } - const edits: ISingleEditOperation[] = []; - for (const edit of change.innerChanges ?? []) { - const newText = this.modifiedModel.getValueInRange(edit.modifiedRange); - edits.push(EditOperation.replace(edit.originalRange, newText)); - } - this.originalModel.pushEditOperations(null, edits, _ => null); - await this._updateDiffInfoSeq(); - if (this._diffInfo.get().identical) { - this._stateObs.set(ModifiedFileEntryState.Accepted, undefined); - this._notifyAction('accepted'); - } - this._accessibilitySignalService.playSignal(AccessibilitySignal.editsKept, { allowManyInParallel: true }); - return true; - } - private async _rejectHunk(change: DetailedLineRangeMapping): Promise { - if (!this._diffInfo.get().changes.includes(change)) { - return false; - } - const edits: ISingleEditOperation[] = []; - for (const edit of change.innerChanges ?? []) { - const newText = this.originalModel.getValueInRange(edit.originalRange); - edits.push(EditOperation.replace(edit.modifiedRange, newText)); - } - this.modifiedModel.pushEditOperations(null, edits, _ => null); - await this._updateDiffInfoSeq(); - if (this._diffInfo.get().identical) { - this._stateObs.set(ModifiedFileEntryState.Rejected, undefined); - this._notifyAction('rejected'); - } - this._accessibilitySignalService.playSignal(AccessibilitySignal.editsUndone, { allowManyInParallel: true }); - return true; - } - - private _applyEdits(edits: ISingleEditOperation[]) { - // make the actual edit - this._isEditFromUs = true; - try { - let result: ISingleEditOperation[] = []; - TextModelEditReason.editWithReason(new TextModelEditReason({ source: 'Chat.applyEdits' }), () => { - this.modifiedModel.pushEditOperations(null, edits, (undoEdits) => { - result = undoEdits; - return null; - }); - }); - return result; - } finally { - this._isEditFromUs = false; - } - } - - private async _updateDiffInfoSeq() { - const myDiffOperationId = ++this._diffOperationIds; - await Promise.resolve(this._diffOperation); - if (this._diffOperationIds === myDiffOperationId) { - const thisDiffOperation = this._updateDiffInfo(); - this._diffOperation = thisDiffOperation; - await thisDiffOperation; - } - } - - private async _updateDiffInfo(): Promise { - - if (this.originalModel.isDisposed() || this.modifiedModel.isDisposed()) { - return undefined; - } - - if (this.state.get() !== ModifiedFileEntryState.Modified) { - this._diffInfo.set(nullDocumentDiff, undefined); - return nullDocumentDiff; - } - - const docVersionNow = this.modifiedModel.getVersionId(); - const snapshotVersionNow = this.originalModel.getVersionId(); - - const diff = await this._editorWorkerService.computeDiff( - this.originalModel.uri, - this.modifiedModel.uri, - { - ignoreTrimWhitespace: false, // NEVER ignore whitespace so that undo/accept edits are correct and so that all changes (1 of 2) are spelled out - computeMoves: false, - maxComputationTimeMs: 3000 - }, - 'advanced' - ); - - if (this.originalModel.isDisposed() || this.modifiedModel.isDisposed()) { - return undefined; - } - - // only update the diff if the documents didn't change in the meantime - if (this.modifiedModel.getVersionId() === docVersionNow && this.originalModel.getVersionId() === snapshotVersionNow) { - const diff2 = diff ?? nullDocumentDiff; - this._diffInfo.set(diff2, undefined); - this._edit = offsetEditFromLineRangeMapping(this.originalModel, this.modifiedModel, diff2.changes); - return diff2; - } - return undefined; - } - - protected override async _doAccept(tx: ITransaction | undefined): Promise { - this.originalModel.setValue(this.modifiedModel.createSnapshot()); - this._diffInfo.set(nullDocumentDiff, tx); - this._edit = StringEdit.empty; - await this._collapse(tx); + protected override async _doAccept(): Promise { + this._textModelChangeService.keep(); + this._multiDiffEntryDelegate.collapse(undefined); const config = this._fileConfigService.getAutoSaveConfiguration(this.modifiedURI); if (!config.autoSave || !this._textFileService.isDirty(this.modifiedURI)) { @@ -513,7 +224,7 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie } } - protected override async _doReject(tx: ITransaction | undefined): Promise { + protected override async _doReject(): Promise { if (this.createdInRequestId === this._telemetryInfo.requestId) { if (isTextFileEditorModel(this._docFileEditorModel)) { await this._docFileEditorModel.revert({ soft: true }); @@ -521,45 +232,21 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie } this._onDidDelete.fire(); } else { - this._setDocValue(this.originalModel.getValue()); - if (this._allEditsAreFromUs && isTextFileEditorModel(this._docFileEditorModel)) { + this._textModelChangeService.undo(); + if (this._textModelChangeService.allEditsAreFromUs && isTextFileEditorModel(this._docFileEditorModel)) { // save the file after discarding so that the dirty indicator goes away // and so that an intermediate saved state gets reverted await this._docFileEditorModel.save({ reason: SaveReason.EXPLICIT, skipSaveParticipants: true }); } - await this._collapse(tx); + this._multiDiffEntryDelegate.collapse(undefined); } } - private _setDocValue(value: string): void { - if (this.modifiedModel.getValue() !== value) { - - this.modifiedModel.pushStackElement(); - const edit = EditOperation.replace(this.modifiedModel.getFullModelRange(), value); - - this._applyEdits([edit]); - this._updateDiffInfoSeq(); - this.modifiedModel.pushStackElement(); - } - } - - private async _collapse(transaction: ITransaction | undefined): Promise { - this._multiDiffEntryDelegate.collapse(transaction); - } - protected _createEditorIntegration(editor: IEditorPane): IModifiedFileEntryEditorIntegration { const codeEditor = getCodeEditor(editor.getControl()); assertType(codeEditor); - const diffInfo = this._diffInfo.map(value => { - return { - ...value, - originalModel: this.originalModel, - modifiedModel: this.modifiedModel, - keep: changes => this._acceptHunk(changes), - undo: changes => this._rejectHunk(changes) - } satisfies IDocumentDiff2; - }); + const diffInfo = this._textModelChangeService.diffInfo; return this._instantiationService.createInstance(ChatEditingCodeEditorIntegration, this, codeEditor, diffInfo, false); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index a6754bc2ce9..9b32f06f85a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -8,9 +8,8 @@ import { Emitter } from '../../../../../base/common/event.js'; import { Disposable, DisposableMap, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../base/common/network.js'; import { clamp } from '../../../../../base/common/numbers.js'; -import { autorun, derived, IObservable, ITransaction, observableValue, observableValueOpts } from '../../../../../base/common/observable.js'; +import { autorun, derived, IObservable, ITransaction, observableValue, observableValueOpts, transaction } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; -import { StringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; @@ -22,8 +21,7 @@ import { IUndoRedoElement, IUndoRedoService } from '../../../../../platform/undo import { IEditorPane } from '../../../../common/editor.js'; import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; -import { IChatAgentResult } from '../../common/chatAgents.js'; -import { ChatEditKind, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; @@ -161,7 +159,7 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im const remain = Math.round(future - Date.now()); if (remain <= 0) { - this.accept(undefined); + this.accept(); } else { const handle = setTimeout(update, 100); this._autoAcceptCtrl.set(new AutoAcceptControl(acceptTimeout, remain, () => { @@ -206,34 +204,38 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im this._telemetryInfo = telemetryInfo; } - async accept(tx: ITransaction | undefined): Promise { + async accept(): Promise { if (this._stateObs.get() !== ModifiedFileEntryState.Modified) { // already accepted or rejected return; } - await this._doAccept(tx); - this._stateObs.set(ModifiedFileEntryState.Accepted, tx); - this._autoAcceptCtrl.set(undefined, tx); + await this._doAccept(); + transaction(tx => { + this._stateObs.set(ModifiedFileEntryState.Accepted, tx); + this._autoAcceptCtrl.set(undefined, tx); + }); this._notifyAction('accepted'); } - protected abstract _doAccept(tx: ITransaction | undefined): Promise; + protected abstract _doAccept(): Promise; - async reject(tx: ITransaction | undefined): Promise { + async reject(): Promise { if (this._stateObs.get() !== ModifiedFileEntryState.Modified) { // already accepted or rejected return; } this._notifyAction('rejected'); - await this._doReject(tx); - this._stateObs.set(ModifiedFileEntryState.Rejected, tx); - this._autoAcceptCtrl.set(undefined, tx); + await this._doReject(); + transaction(tx => { + this._stateObs.set(ModifiedFileEntryState.Rejected, tx); + this._autoAcceptCtrl.set(undefined, tx); + }); } - protected abstract _doReject(tx: ITransaction | undefined): Promise; + protected abstract _doReject(): Promise; protected _notifyAction(outcome: 'accepted' | 'rejected' | 'userModified') { this._chatService.notifyUserAction({ @@ -283,18 +285,18 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im abstract acceptAgentEdits(uri: URI, edits: (TextEdit | ICellEditOperation)[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise; - async acceptStreamingEditsEnd(tx: ITransaction) { - this._resetEditsState(tx); + async acceptStreamingEditsEnd() { + this._resetEditsState(undefined); if (await this._areOriginalAndModifiedIdentical()) { // ACCEPT if identical - await this.accept(tx); + await this.accept(); } } protected abstract _areOriginalAndModifiedIdentical(): Promise; - protected _resetEditsState(tx: ITransaction): void { + protected _resetEditsState(tx: ITransaction | undefined): void { this._isCurrentlyBeingModifiedByObs.set(undefined, tx); this._rewriteRatioObs.set(0, tx); this._waitsForLastEdits.set(false, tx); @@ -306,30 +308,11 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im abstract equalsSnapshot(snapshot: ISnapshotEntry | undefined): boolean; - abstract restoreFromSnapshot(snapshot: ISnapshotEntry, restoreToDisk?: boolean): void; + abstract restoreFromSnapshot(snapshot: ISnapshotEntry, restoreToDisk?: boolean): Promise; // --- inital content - abstract resetToInitialContent(): void; + abstract resetToInitialContent(): Promise; abstract initialContent: string; } - -export interface IModifiedEntryTelemetryInfo { - readonly agentId: string | undefined; - readonly command: string | undefined; - readonly sessionId: string; - readonly requestId: string; - readonly result: IChatAgentResult | undefined; -} - -export interface ISnapshotEntry { - readonly resource: URI; - readonly languageId: string; - readonly snapshotUri: URI; - readonly original: string; - readonly current: string; - readonly originalToCurrentEdit: StringEdit; - readonly state: ModifiedFileEntryState; - telemetryInfo: IModifiedEntryTelemetryInfo; -} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index 9967b9030b8..6d5f1d8d23a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -15,7 +15,6 @@ import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { LineRange } from '../../../../../editor/common/core/ranges/lineRange.js'; -import { StringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { nullDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; import { DetailedLineRangeMapping, RangeMapping } from '../../../../../editor/common/diff/rangeMapping.js'; @@ -43,10 +42,10 @@ import { INotebookEditorModelResolverService } from '../../../notebook/common/no import { INotebookLoggingService } from '../../../notebook/common/notebookLoggingService.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; import { INotebookEditorWorkerService } from '../../../notebook/common/services/notebookWorkerService.js'; -import { ChatEditKind, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; -import { AbstractChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; +import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; import { createSnapshot, deserializeSnapshot, getNotebookSnapshotFileURI, restoreSnapshot, SnapshotComparer } from './notebook/chatEditingModifiedNotebookSnapshot.js'; import { ChatEditingNewNotebookContentEdits } from './notebook/chatEditingNewNotebookContentEdits.js'; import { ChatEditingNotebookCellEntry } from './notebook/chatEditingNotebookCellEntry.js'; @@ -412,12 +411,12 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } } - protected override async _doAccept(tx: ITransaction | undefined): Promise { - this.updateCellDiffInfo([], tx); + protected override async _doAccept(): Promise { + this.updateCellDiffInfo([], undefined); const snapshot = createSnapshot(this.modifiedModel, this.transientOptions, this.configurationService); restoreSnapshot(this.originalModel, snapshot); this.initializeModelsFromDiff(); - await this._collapse(tx); + await this._collapse(undefined); const config = this._fileConfigService.getAutoSaveConfiguration(this.modifiedURI); if (this.modifiedModel.uri.scheme !== Schemas.untitled && (!config.autoSave || !this.notebookResolver.isDirty(this.modifiedURI))) { @@ -436,8 +435,8 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } } - protected override async _doReject(tx: ITransaction | undefined): Promise { - this.updateCellDiffInfo([], tx); + protected override async _doReject(): Promise { + this.updateCellDiffInfo([], undefined); if (this.createdInRequestId === this._telemetryInfo.requestId) { await this._applyEdits(async () => { await this.modifiedResourceRef.object.revert({ soft: true }); @@ -455,7 +454,7 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } }); this.initializeModelsFromDiff(); - await this._collapse(tx); + await this._collapse(undefined); } } @@ -554,17 +553,17 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } // For all cells that were edited, send the `isLastEdits` flag. - const finishPreviousCells = () => { - this.editedCells.forEach(uri => { + const finishPreviousCells = async () => { + await Promise.all(Array.from(this.editedCells).map(async (uri) => { const cell = this.modifiedModel.cells.find(cell => isEqual(cell.uri, uri)); const cellEntry = cell && this.cellEntryMap.get(cell.uri); - cellEntry?.acceptAgentEdits([], true, responseModel); - }); + await cellEntry?.acceptAgentEdits([], true, responseModel); + })); this.editedCells.clear(); }; - this._applyEditsSync(async () => { - edits.map((edit, idx) => { + this._applyEdits(async () => { + await Promise.all(edits.map(async (edit, idx) => { const last = isLastEdits && idx === edits.length - 1; if (TextEdit.isTextEdit(edit)) { // Possible we're getting the raw content for the notebook. @@ -575,22 +574,22 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie // If we get cell edits, its impossible to get text edits for the notebook uri. this.newNotebookEditGenerator = undefined; if (!this.editedCells.has(resource)) { - finishPreviousCells(); + await finishPreviousCells(); this.editedCells.add(resource); } - cellEntry?.acceptAgentEdits([edit], last, responseModel); + await cellEntry?.acceptAgentEdits([edit], last, responseModel); } } else { // If we notebook edits, its impossible to get text edits for the notebook uri. this.newNotebookEditGenerator = undefined; this.acceptNotebookEdit(edit); } - }); + })); }); // If the last edit for a cell was sent, then handle it if (isLastEdits) { - finishPreviousCells(); + await finishPreviousCells(); } // isLastEdits can be true for cell Uris, but when its true for Cells edits. @@ -612,7 +611,6 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie const newRewriteRation = Math.max(this._rewriteRatioObs.get(), calculateNotebookRewriteRatio(this._cellsDiffInfo.get(), this.originalModel, this.modifiedModel)); this._rewriteRatioObs.set(Math.min(1, newRewriteRation), tx); } else { - finishPreviousCells(); this.editedCells.clear(); this._resetEditsState(tx); this._rewriteRatioObs.set(1, tx); @@ -918,7 +916,6 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie snapshotUri: getNotebookSnapshotFileURI(this._telemetryInfo.sessionId, requestId, undoStop, this.modifiedURI.path, this.modifiedModel.viewType), original: createSnapshot(this.originalModel, this.transientOptions, this.configurationService), current: createSnapshot(this.modifiedModel, this.transientOptions, this.configurationService), - originalToCurrentEdit: StringEdit.empty, state: this.state.get(), telemetryInfo: this.telemetryInfo, }; @@ -933,7 +930,7 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } - override restoreFromSnapshot(snapshot: ISnapshotEntry, restoreToDisk = true): void { + override async restoreFromSnapshot(snapshot: ISnapshotEntry, restoreToDisk = true): Promise { this.updateCellDiffInfo([], undefined); this._stateObs.set(snapshot.state, undefined); restoreSnapshot(this.originalModel, snapshot.original); @@ -943,7 +940,7 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie this.initializeModelsFromDiff(); } - override resetToInitialContent(): void { + override async resetToInitialContent(): Promise { this.updateCellDiffInfo([], undefined); this.restoreSnapshotInModifiedModel(this.initialContent); this.initializeModelsFromDiff(); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 9f89be21ccd..d5a2a10f61a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -12,7 +12,7 @@ import { Emitter } from '../../../../../base/common/event.js'; import { Iterable } from '../../../../../base/common/iterator.js'; import { Disposable, DisposableStore, dispose } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; -import { asyncTransaction, autorun, derived, derivedOpts, IObservable, IReader, ITransaction, ObservablePromise, observableValue, transaction } from '../../../../../base/common/observable.js'; +import { autorun, derived, derivedOpts, IObservable, IReader, ITransaction, ObservablePromise, observableValue, transaction } from '../../../../../base/common/observable.js'; import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; @@ -35,11 +35,11 @@ import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEdito import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { CellUri, ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; -import { ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IEditSessionEntryDiff, IModifiedFileEntry, IStreamingEdits, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IEditSessionEntryDiff, IModifiedEntryTelemetryInfo, IModifiedFileEntry, ISnapshotEntry, IStreamingEdits, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatRequestDisablement, IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatEditingModifiedDocumentEntry } from './chatEditingModifiedDocumentEntry.js'; -import { AbstractChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; +import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; import { ChatEditingModifiedNotebookEntry } from './chatEditingModifiedNotebookEntry.js'; import { ChatEditingSessionStorage, IChatEditingSessionSnapshot, IChatEditingSessionStop, StoredSessionState } from './chatEditingSessionStorage.js'; import { ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; @@ -224,9 +224,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio for (const [uri, content] of restoredSessionState.initialFileContents) { this._initialFileContents.set(uri, content); } - await asyncTransaction(async tx => { - this._pendingSnapshot = restoredSessionState.pendingSnapshot; - await this._restoreSnapshot(restoredSessionState.recentSnapshot, tx, false); + this._pendingSnapshot = restoredSessionState.pendingSnapshot; + await this._restoreSnapshot(restoredSessionState.recentSnapshot, false); + transaction(async tx => { this._linearHistory.set(restoredSessionState.linearHistory, tx); this._linearHistoryIndex.set(restoredSessionState.linearHistoryIndex, tx); this._state.set(ChatEditingSessionState.Idle, tx); @@ -420,7 +420,10 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio const linearHistoryPtr = this._linearHistoryIndex.get(); const newLinearHistory: IChatEditingSessionSnapshot[] = []; for (const entry of this._linearHistory.get()) { - if (linearHistoryPtr - entry.startIndex < entry.stops.length) { + if (entry.startIndex >= linearHistoryPtr) { + // all further entries are being dropped + break; + } else if (linearHistoryPtr - entry.startIndex < entry.stops.length) { newLinearHistory.push({ requestId: entry.requestId, stops: entry.stops.slice(0, linearHistoryPtr - entry.startIndex), startIndex: entry.startIndex, postEdit: undefined }); } else { newLinearHistory.push(entry); @@ -491,10 +494,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio const stopRef = this._findEditStop(requestId, stopId); if (stopRef) { this._ensurePendingSnapshot(); - await asyncTransaction(async tx => { - this._linearHistoryIndex.set(stopRef.historyIndex, tx); - await this._restoreSnapshot(stopRef.stop, tx); - }); + this._linearHistoryIndex.set(stopRef.historyIndex, undefined); + await this._restoreSnapshot(stopRef.stop); this._updateRequestHiddenState(); } } else { @@ -507,14 +508,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } } - private async _restoreSnapshot({ entries }: IChatEditingSessionStop, tx: ITransaction | undefined, restoreResolvedToDisk = true): Promise { + private async _restoreSnapshot({ entries }: IChatEditingSessionStop, restoreResolvedToDisk = true): Promise { // Reset all the files which are modified in this session state // but which are not found in the snapshot for (const entry of this._entriesObs.get()) { const snapshotEntry = entries.get(entry.modifiedURI); if (!snapshotEntry) { - entry.resetToInitialContent(); + await entry.resetToInitialContent(); entry.dispose(); } } @@ -524,33 +525,11 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio for (const snapshotEntry of entries.values()) { const entry = await this._getOrCreateModifiedFileEntry(snapshotEntry.resource, snapshotEntry.telemetryInfo); const restoreToDisk = snapshotEntry.state === ModifiedFileEntryState.Modified || restoreResolvedToDisk; - entry.restoreFromSnapshot(snapshotEntry, restoreToDisk); + await entry.restoreFromSnapshot(snapshotEntry, restoreToDisk); entriesArr.push(entry); } - this._entriesObs.set(entriesArr, tx); - } - - remove(...uris: URI[]): void { - this._assertNotDisposed(); - - let didRemoveUris = false; - for (const uri of uris) { - - const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); - if (entry) { - entry.dispose(); - const newEntries = this._entriesObs.get().filter(e => !isEqual(e.modifiedURI, uri)); - this._entriesObs.set(newEntries, undefined); - didRemoveUris = true; - } - - } - - if (!didRemoveUris) { - return; // noop - } - + this._entriesObs.set(entriesArr, undefined); } private _assertNotDisposed(): void { @@ -562,37 +541,32 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio async accept(...uris: URI[]): Promise { this._assertNotDisposed(); - await asyncTransaction(async tx => { + if (uris.length === 0) { + await Promise.all(this._entriesObs.get().map(entry => entry.accept())); + } - if (uris.length === 0) { - await Promise.all(this._entriesObs.get().map(entry => entry.accept(tx))); + for (const uri of uris) { + const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); + if (entry) { + await entry.accept(); } - - for (const uri of uris) { - const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); - if (entry) { - await entry.accept(tx); - } - } - }); + } this._accessibilitySignalService.playSignal(AccessibilitySignal.editsKept, { allowManyInParallel: true }); } async reject(...uris: URI[]): Promise { this._assertNotDisposed(); - await asyncTransaction(async tx => { - if (uris.length === 0) { - await Promise.all(this._entriesObs.get().map(entry => entry.reject(tx))); - } + if (uris.length === 0) { + await Promise.all(this._entriesObs.get().map(entry => entry.reject())); + } - for (const uri of uris) { - const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); - if (entry) { - await entry.reject(tx); - } + for (const uri of uris) { + const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); + if (entry) { + await entry.reject(); } - }); + } this._accessibilitySignalService.playSignal(AccessibilitySignal.editsUndone, { allowManyInParallel: true }); } @@ -738,10 +712,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } this._ensurePendingSnapshot(); - await asyncTransaction(async tx => { - await this._restoreSnapshot(previousSnapshot.stop, tx); - this._linearHistoryIndex.set(newIndex, tx); - }); + await this._restoreSnapshot(previousSnapshot.stop); + this._linearHistoryIndex.set(newIndex, undefined); this._updateRequestHiddenState(); } @@ -756,10 +728,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio if (!nextSnapshot) { return; } - await asyncTransaction(async tx => { - await this._restoreSnapshot(nextSnapshot, tx); - this._linearHistoryIndex.set(newIndex, tx); - }); + await this._restoreSnapshot(nextSnapshot); + this._linearHistoryIndex.set(newIndex, undefined); this._updateRequestHiddenState(); } @@ -809,7 +779,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio * * @param next If true, this will edit the snapshot _after_ the undo stop */ - private ensureEditInUndoStopMatches(requestId: string, undoStop: string | undefined, entry: AbstractChatEditingModifiedFileEntry, next: boolean, tx: ITransaction) { + private ensureEditInUndoStopMatches(requestId: string, undoStop: string | undefined, entry: AbstractChatEditingModifiedFileEntry, next: boolean, tx: ITransaction | undefined) { const history = this._linearHistory.get(); const snapIndex = history.findIndex(s => s.requestId === requestId); if (snapIndex === -1) { @@ -871,20 +841,19 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } private async _resolve(requestId: string, undoStop: string | undefined, resource: URI): Promise { - await asyncTransaction(async (tx) => { - const hasOtherTasks = Iterable.some(this._streamingEditLocks.keys(), k => k !== resource.toString()); - if (!hasOtherTasks) { - this._state.set(ChatEditingSessionState.Idle, tx); - } - const entry = this._getEntry(resource); - if (!entry) { - return; - } + const hasOtherTasks = Iterable.some(this._streamingEditLocks.keys(), k => k !== resource.toString()); + if (!hasOtherTasks) { + this._state.set(ChatEditingSessionState.Idle, undefined); + } - this.ensureEditInUndoStopMatches(requestId, undoStop, entry, /* next= */ true, tx); - return entry.acceptStreamingEditsEnd(tx); - }); + const entry = this._getEntry(resource); + if (!entry) { + return; + } + + this.ensureEditInUndoStopMatches(requestId, undoStop, entry, /* next= */ true, undefined); + return entry.acceptStreamingEditsEnd(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts index b44fc546d1f..d755618c797 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts @@ -8,13 +8,11 @@ import { StringSHA1 } from '../../../../../base/common/hash.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { joinPath } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; -import { StringEdit, ISerializedStringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; -import { ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; -import { WorkingSetDisplayMetadata, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { WorkingSetDisplayMetadata, ModifiedFileEntryState, ISnapshotEntry } from '../../common/chatEditingService.js'; const STORAGE_CONTENTS_FOLDER = 'contents'; const STORAGE_STATE_FILE = 'state.json'; @@ -80,7 +78,6 @@ export class ChatEditingSessionStorage { languageId: entry.languageId, original: await getFileContent(entry.originalHash), current: await getFileContent(entry.currentHash), - originalToCurrentEdit: StringEdit.fromJson(entry.originalToCurrentEdit), state: entry.state, snapshotUri: URI.parse(entry.snapshotUri), telemetryInfo: { requestId: entry.telemetryInfo.requestId, agentId: entry.telemetryInfo.agentId, command: entry.telemetryInfo.command, sessionId: this.chatSessionId, result: undefined } @@ -180,7 +177,6 @@ export class ChatEditingSessionStorage { languageId: entry.languageId, originalHash: addFileContent(entry.original), currentHash: addFileContent(entry.current), - originalToCurrentEdit: entry.originalToCurrentEdit.toJson(), state: entry.state, snapshotUri: entry.snapshotUri.toString(), telemetryInfo: { requestId: entry.telemetryInfo.requestId, agentId: entry.telemetryInfo.agentId, command: entry.telemetryInfo.command } @@ -275,7 +271,6 @@ interface ISnapshotEntryDTO { readonly languageId: string; readonly originalHash: string; readonly currentHash: string; - readonly originalToCurrentEdit: ISerializedStringEdit; readonly state: ModifiedFileEntryState; readonly snapshotUri: string; readonly telemetryInfo: IModifiedEntryTelemetryInfoDTO; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts new file mode 100644 index 00000000000..d4d42d87356 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts @@ -0,0 +1,396 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener, getWindow } from '../../../../../base/browser/dom.js'; +import { assert } from '../../../../../base/common/assert.js'; +import { DeferredPromise, RunOnceScheduler, timeout } from '../../../../../base/common/async.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { IObservable, observableValue } from '../../../../../base/common/observable.js'; +import { isEqual } from '../../../../../base/common/resources.js'; +import { themeColorFromId } from '../../../../../base/common/themables.js'; +import { assertType } from '../../../../../base/common/types.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { EditOperation, ISingleEditOperation } from '../../../../../editor/common/core/editOperation.js'; +import { StringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { IDocumentDiff, nullDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; +import { DetailedLineRangeMapping } from '../../../../../editor/common/diff/rangeMapping.js'; +import { TextEdit } from '../../../../../editor/common/languages.js'; +import { IModelDeltaDecoration, ITextModel, ITextSnapshot, MinimapPosition, OverviewRulerLane } from '../../../../../editor/common/model.js'; +import { ModelDecorationOptions } from '../../../../../editor/common/model/textModel.js'; +import { offsetEditFromContentChanges, offsetEditFromLineRangeMapping, offsetEditToEditOperations } from '../../../../../editor/common/model/textModelStringEdit.js'; +import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; +import { TextModelEditReason } from '../../../../../editor/common/textModelEditReason.js'; +import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; +import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js'; +import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; +import { ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { IDocumentDiff2 } from './chatEditingCodeEditorIntegration.js'; +import { pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js'; + + +export class ChatEditingTextModelChangeService extends Disposable { + + private static readonly _lastEditDecorationOptions = ModelDecorationOptions.register({ + isWholeLine: true, + description: 'chat-last-edit', + className: 'chat-editing-last-edit-line', + marginClassName: 'chat-editing-last-edit', + overviewRuler: { + position: OverviewRulerLane.Full, + color: themeColorFromId(editorSelectionBackground) + }, + }); + + private static readonly _pendingEditDecorationOptions = ModelDecorationOptions.register({ + isWholeLine: true, + description: 'chat-pending-edit', + className: 'chat-editing-pending-edit', + minimap: { + position: MinimapPosition.Inline, + color: themeColorFromId(pendingRewriteMinimap) + } + }); + + private static readonly _atomicEditDecorationOptions = ModelDecorationOptions.register({ + isWholeLine: true, + description: 'chat-atomic-edit', + className: 'chat-editing-atomic-edit', + minimap: { + position: MinimapPosition.Inline, + color: themeColorFromId(pendingRewriteMinimap) + } + }); + + private _isEditFromUs: boolean = false; + public get isEditFromUs() { + return this._isEditFromUs; + } + private _allEditsAreFromUs: boolean = true; + public get allEditsAreFromUs() { + return this._allEditsAreFromUs; + } + private _diffOperation: Promise | undefined; + private _diffOperationIds: number = 0; + + private readonly _diffInfo = observableValue(this, nullDocumentDiff); + public get diffInfo() { + return this._diffInfo.map(value => { + return { + ...value, + originalModel: this.originalModel, + modifiedModel: this.modifiedModel, + keep: changes => this._keepHunk(changes), + undo: changes => this._undoHunk(changes) + } satisfies IDocumentDiff2; + }); + } + + private readonly _editDecorationClear = this._register(new RunOnceScheduler(() => { this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, []); }, 500)); + private _editDecorations: string[] = []; + + private readonly _didAcceptOrRejectAllHunks = this._register(new Emitter()); + public readonly onDidAcceptOrRejectAllHunks = this._didAcceptOrRejectAllHunks.event; + + private readonly _didUserEditModel = this._register(new Emitter()); + public readonly onDidUserEditModel = this._didUserEditModel.event; + + private _originalToModifiedEdit: StringEdit = StringEdit.empty; + + constructor( + private readonly originalModel: ITextModel, + private readonly modifiedModel: ITextModel, + private readonly state: IObservable, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, + ) { + super(); + this._register(this.modifiedModel.onDidChangeContent(e => { + this._mirrorEdits(e); + })); + + this._register(toDisposable(() => { + this.clearCurrentEditLineDecoration(); + })); + } + + public clearCurrentEditLineDecoration() { + this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, []); + } + + public async areOriginalAndModifiedIdentical(): Promise { + const diff = await this._diffOperation; + return diff ? diff.identical : false; + } + + async acceptAgentEdits(resource: URI, textEdits: (TextEdit | ICellEditOperation)[], isLastEdits: boolean): Promise<{ rewriteRatio: number; maxLineNumber: number }> { + + assertType(textEdits.every(TextEdit.isTextEdit), 'INVALID args, can only handle text edits'); + assert(isEqual(resource, this.modifiedModel.uri), ' INVALID args, can only edit THIS document'); + + const isAtomicEdits = textEdits.length > 0 && isLastEdits; + let maxLineNumber = 0; + let rewriteRatio = 0; + + if (isAtomicEdits) { + // EDIT and DONE + const minimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this.modifiedModel.uri, textEdits) ?? textEdits; + const ops = minimalEdits.map(TextEdit.asEditOperation); + const undoEdits = this._applyEdits(ops); + + if (undoEdits.length > 0) { + let range: Range | undefined; + for (let i = 0; i < undoEdits.length; i++) { + const op = undoEdits[i]; + if (!range) { + range = Range.lift(op.range); + } else { + range = Range.plusRange(range, op.range); + } + } + if (range) { + + const defer = new DeferredPromise(); + const listener = addDisposableListener(getWindow(undefined), 'animationend', e => { + if (e.animationName === 'kf-chat-editing-atomic-edit') { // CHECK with chat.css + defer.complete(); + listener.dispose(); + } + }); + + this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, [{ + options: ChatEditingTextModelChangeService._atomicEditDecorationOptions, + range + }]); + + await Promise.any([defer.p, timeout(500)]); // wait for animation to finish but also time-cap it + listener.dispose(); + } + } + + + } else { + // EDIT a bit, then DONE + const ops = textEdits.map(TextEdit.asEditOperation); + const undoEdits = this._applyEdits(ops); + maxLineNumber = undoEdits.reduce((max, op) => Math.max(max, op.range.startLineNumber), 0); + rewriteRatio = Math.min(1, maxLineNumber / this.modifiedModel.getLineCount()); + + const newDecorations: IModelDeltaDecoration[] = [ + // decorate pending edit (region) + { + options: ChatEditingTextModelChangeService._pendingEditDecorationOptions, + range: new Range(maxLineNumber + 1, 1, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) + } + ]; + + if (maxLineNumber > 0) { + // decorate last edit + newDecorations.push({ + options: ChatEditingTextModelChangeService._lastEditDecorationOptions, + range: new Range(maxLineNumber, 1, maxLineNumber, Number.MAX_SAFE_INTEGER) + }); + } + this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, newDecorations); + + } + + if (isLastEdits) { + this._updateDiffInfoSeq(); + this._editDecorationClear.schedule(); + } + + return { rewriteRatio, maxLineNumber }; + } + + private _applyEdits(edits: ISingleEditOperation[]) { + try { + this._isEditFromUs = true; + // make the actual edit + let result: ISingleEditOperation[] = []; + TextModelEditReason.editWithReason(new TextModelEditReason({ source: 'Chat.applyEdits' }), () => { + this.modifiedModel.pushEditOperations(null, edits, (undoEdits) => { + result = undoEdits; + return null; + }); + }); + return result; + } finally { + this._isEditFromUs = false; + } + } + + /** + * Keeps the current modified document as the final contents. + */ + public keep() { + this.originalModel.setValue(this.modifiedModel.createSnapshot()); + this._diffInfo.set(nullDocumentDiff, undefined); + this._originalToModifiedEdit = StringEdit.empty; + } + + /** + * Undoes the current modified document as the final contents. + */ + public undo() { + this.modifiedModel.pushStackElement(); + this._applyEdits([(EditOperation.replace(this.modifiedModel.getFullModelRange(), this.originalModel.getValue()))]); + this.modifiedModel.pushStackElement(); + this._originalToModifiedEdit = StringEdit.empty; + this._diffInfo.set(nullDocumentDiff, undefined); + } + + public async resetDocumentValues(newOriginal: string | ITextSnapshot | undefined, newModified: string | undefined): Promise { + let didChange = false; + if (newOriginal !== undefined) { + this.originalModel.setValue(newOriginal); + didChange = true; + } + if (newModified !== undefined && this.modifiedModel.getValue() !== newModified) { + // NOTE that this isn't done via `setValue` so that the undo stack is preserved + this.modifiedModel.pushStackElement(); + this._applyEdits([(EditOperation.replace(this.modifiedModel.getFullModelRange(), newModified))]); + this.modifiedModel.pushStackElement(); + didChange = true; + } + if (didChange) { + await this._updateDiffInfoSeq(); + } + } + + private _mirrorEdits(event: IModelContentChangedEvent) { + const edit = offsetEditFromContentChanges(event.changes); + + if (this._isEditFromUs) { + const e_sum = this._originalToModifiedEdit; + const e_ai = edit; + this._originalToModifiedEdit = e_sum.compose(e_ai); + } else { + + // e_ai + // d0 ---------------> s0 + // | | + // | | + // | e_user_r | e_user + // | | + // | | + // v e_ai_r v + /// d1 ---------------> s1 + // + // d0 - document snapshot + // s0 - document + // e_ai - ai edits + // e_user - user edits + // + const e_ai = this._originalToModifiedEdit; + const e_user = edit; + + const e_user_r = e_user.tryRebase(e_ai.inverse(this.originalModel.getValue()), true); + + if (e_user_r === undefined) { + // user edits overlaps/conflicts with AI edits + this._originalToModifiedEdit = e_ai.compose(e_user); + } else { + const edits = offsetEditToEditOperations(e_user_r, this.originalModel); + this.originalModel.applyEdits(edits); + this._originalToModifiedEdit = e_ai.tryRebase(e_user_r); + } + + this._allEditsAreFromUs = false; + this._updateDiffInfoSeq(); + this._didUserEditModel.fire(); + } + } + + private async _keepHunk(change: DetailedLineRangeMapping): Promise { + if (!this._diffInfo.get().changes.includes(change)) { + // diffInfo should have model version ids and check them (instead of the caller doing that) + return false; + } + const edits: ISingleEditOperation[] = []; + for (const edit of change.innerChanges ?? []) { + const newText = this.modifiedModel.getValueInRange(edit.modifiedRange); + edits.push(EditOperation.replace(edit.originalRange, newText)); + } + this.originalModel.pushEditOperations(null, edits, _ => null); + await this._updateDiffInfoSeq(); + if (this._diffInfo.get().identical) { + this._didAcceptOrRejectAllHunks.fire(ModifiedFileEntryState.Accepted); + } + this._accessibilitySignalService.playSignal(AccessibilitySignal.editsKept, { allowManyInParallel: true }); + return true; + } + + private async _undoHunk(change: DetailedLineRangeMapping): Promise { + if (!this._diffInfo.get().changes.includes(change)) { + return false; + } + const edits: ISingleEditOperation[] = []; + for (const edit of change.innerChanges ?? []) { + const newText = this.originalModel.getValueInRange(edit.originalRange); + edits.push(EditOperation.replace(edit.modifiedRange, newText)); + } + this.modifiedModel.pushEditOperations(null, edits, _ => null); + await this._updateDiffInfoSeq(); + if (this._diffInfo.get().identical) { + this._didAcceptOrRejectAllHunks.fire(ModifiedFileEntryState.Rejected); + } + this._accessibilitySignalService.playSignal(AccessibilitySignal.editsUndone, { allowManyInParallel: true }); + return true; + } + + + private async _updateDiffInfoSeq() { + const myDiffOperationId = ++this._diffOperationIds; + await Promise.resolve(this._diffOperation); + if (this._diffOperationIds === myDiffOperationId) { + const thisDiffOperation = this._updateDiffInfo(); + this._diffOperation = thisDiffOperation; + await thisDiffOperation; + } + } + + private async _updateDiffInfo(): Promise { + + if (this.originalModel.isDisposed() || this.modifiedModel.isDisposed() || this._store.isDisposed) { + return undefined; + } + + if (this.state.get() !== ModifiedFileEntryState.Modified) { + this._diffInfo.set(nullDocumentDiff, undefined); + this._originalToModifiedEdit = StringEdit.empty; + return nullDocumentDiff; + } + + const docVersionNow = this.modifiedModel.getVersionId(); + const snapshotVersionNow = this.originalModel.getVersionId(); + + const diff = await this._editorWorkerService.computeDiff( + this.originalModel.uri, + this.modifiedModel.uri, + { + ignoreTrimWhitespace: false, // NEVER ignore whitespace so that undo/accept edits are correct and so that all changes (1 of 2) are spelled out + computeMoves: false, + maxComputationTimeMs: 3000 + }, + 'advanced' + ); + + if (this.originalModel.isDisposed() || this.modifiedModel.isDisposed() || this._store.isDisposed) { + return undefined; + } + + // only update the diff if the documents didn't change in the meantime + if (this.modifiedModel.getVersionId() === docVersionNow && this.originalModel.getVersionId() === snapshotVersionNow) { + const diff2 = diff ?? nullDocumentDiff; + this._diffInfo.set(diff2, undefined); + this._originalToModifiedEdit = offsetEditFromLineRangeMapping(this.originalModel, this.modifiedModel, diff2.changes); + return diff2; + } + return undefined; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts index 20b3d767623..a2654b956bc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts @@ -8,7 +8,6 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelContentProvider } from '../../../../../editor/common/services/resolverService.js'; import { chatEditingSnapshotScheme, IChatEditingService } from '../../common/chatEditingService.js'; -import { ChatEditingSession } from './chatEditingSession.js'; type ChatEditingTextModelContentQueryData = { kind: 'doc'; documentId: string; chatSessionId: string }; @@ -72,7 +71,7 @@ export class ChatEditingSnapshotTextModelContentProvider implements ITextModelCo const data: ChatEditingSnapshotTextModelContentQueryData = JSON.parse(resource.query); const session = this._chatEditingService.getEditingSession(data.sessionId); - if (!(session instanceof ChatEditingSession) || !data.requestId) { + if (!session || !data.requestId) { return null; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts index 5521a153318..4d7726defd0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts @@ -8,8 +8,8 @@ import { computeDiff } from '../../../../notebook/common/notebookDiff.js'; import { INotebookEditorModelResolverService } from '../../../../notebook/common/notebookEditorModelResolverService.js'; import { INotebookLoggingService } from '../../../../notebook/common/notebookLoggingService.js'; import { INotebookEditorWorkerService } from '../../../../notebook/common/services/notebookWorkerService.js'; -import { IEditSessionEntryDiff } from '../../../common/chatEditingService.js'; -import { ISnapshotEntry } from '../chatEditingModifiedFileEntry.js'; +import { IEditSessionEntryDiff, ISnapshotEntry } from '../../../common/chatEditingService.js'; + export class ChatEditingModifiedNotebookDiff { static NewModelCounter: number = 0; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts index 0913bbf6a92..b5f099d2a95 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts @@ -3,33 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RunOnceScheduler } from '../../../../../../base/common/async.js'; -import { DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { ITransaction, IObservable, observableValue, autorun, transaction } from '../../../../../../base/common/observable.js'; -import { ObservableDisposable } from '../../../../../../base/common/observableDisposable.js'; -import { themeColorFromId } from '../../../../../../base/common/themables.js'; +import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { IObservable, observableValue, transaction } from '../../../../../../base/common/observable.js'; import { URI } from '../../../../../../base/common/uri.js'; -import { EditOperation, ISingleEditOperation } from '../../../../../../editor/common/core/editOperation.js'; -import { StringEdit } from '../../../../../../editor/common/core/edits/stringEdit.js'; -import { Range } from '../../../../../../editor/common/core/range.js'; -import { IDocumentDiff, nullDocumentDiff } from '../../../../../../editor/common/diff/documentDiffProvider.js'; +import { IDocumentDiff } from '../../../../../../editor/common/diff/documentDiffProvider.js'; import { DetailedLineRangeMapping } from '../../../../../../editor/common/diff/rangeMapping.js'; import { TextEdit } from '../../../../../../editor/common/languages.js'; -import { IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane } from '../../../../../../editor/common/model.js'; -import { ModelDecorationOptions } from '../../../../../../editor/common/model/textModel.js'; -import { offsetEditFromContentChanges, offsetEditFromLineRangeMapping, offsetEditToEditOperations } from '../../../../../../editor/common/model/textModelStringEdit.js'; -import { IEditorWorkerService } from '../../../../../../editor/common/services/editorWorker.js'; -import { IModelContentChangedEvent } from '../../../../../../editor/common/textModelEvents.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { observableConfigValue } from '../../../../../../platform/observable/common/platformObservableUtils.js'; -import { editorSelectionBackground } from '../../../../../../platform/theme/common/colorRegistry.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { CellEditState } from '../../../../notebook/browser/notebookBrowser.js'; import { INotebookEditorService } from '../../../../notebook/browser/services/notebookEditorService.js'; import { NotebookCellTextModel } from '../../../../notebook/common/model/notebookCellTextModel.js'; import { CellKind } from '../../../../notebook/common/notebookCommon.js'; import { ModifiedFileEntryState } from '../../../common/chatEditingService.js'; import { IChatResponseModel } from '../../../common/chatModel.js'; -import { pendingRewriteMinimap } from '../chatEditingModifiedFileEntry.js'; +import { ChatEditingTextModelChangeService } from '../chatEditingTextModelChangeService.js'; /** @@ -37,199 +25,77 @@ import { pendingRewriteMinimap } from '../chatEditingModifiedFileEntry.js'; * Most of the code has been borrowed from there, as a cell is effectively a document. * Hence most of the same functionality applies. */ -export class ChatEditingNotebookCellEntry extends ObservableDisposable { - private static readonly _lastEditDecorationOptions = ModelDecorationOptions.register({ - isWholeLine: true, - description: 'chat-last-edit', - className: 'chat-editing-last-edit-line', - marginClassName: 'chat-editing-last-edit', - overviewRuler: { - position: OverviewRulerLane.Full, - color: themeColorFromId(editorSelectionBackground) - }, - }); +export class ChatEditingNotebookCellEntry extends Disposable { + public get isDisposed(): boolean { + return this._store.isDisposed; + } - private static readonly _pendingEditDecorationOptions = ModelDecorationOptions.register({ - isWholeLine: true, - description: 'chat-pending-edit', - className: 'chat-editing-pending-edit', - minimap: { - position: MinimapPosition.Inline, - color: themeColorFromId(pendingRewriteMinimap) - } - }); - - - private _edit: StringEdit = StringEdit.empty; - private _isEditFromUs: boolean = false; public get isEditFromUs(): boolean { - return this._isEditFromUs; + return this._textModelChangeService.isEditFromUs; } - private _allEditsAreFromUs: boolean = true; public get allEditsAreFromUs(): boolean { - return this._allEditsAreFromUs; + return this._textModelChangeService.allEditsAreFromUs; } - private _diffOperation: Promise | undefined; - private _diffOperationIds: number = 0; - - private readonly _diffInfo = observableValue(this, nullDocumentDiff); public get diffInfo(): IObservable { - return this._diffInfo; + return this._textModelChangeService.diffInfo; } private readonly _maxModifiedLineNumber = observableValue(this, 0); readonly maxModifiedLineNumber = this._maxModifiedLineNumber; - private readonly _editDecorationClear = this._register(new RunOnceScheduler(() => { this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, []); }, 500)); - private _editDecorations: string[] = []; - - private readonly _diffTrimWhitespace: IObservable; protected readonly _stateObs = observableValue(this, ModifiedFileEntryState.Modified); readonly state: IObservable = this._stateObs; - protected readonly _isCurrentlyBeingModifiedByObs = observableValue(this, undefined); - readonly isCurrentlyBeingModifiedBy: IObservable = this._isCurrentlyBeingModifiedByObs; private readonly initialContent: string; - + private readonly _textModelChangeService: ChatEditingTextModelChangeService; constructor( public readonly notebookUri: URI, public readonly cell: NotebookCellTextModel, private readonly modifiedModel: ITextModel, private readonly originalModel: ITextModel, disposables: DisposableStore, - @IConfigurationService configService: IConfigurationService, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @INotebookEditorService private readonly notebookEditorService: INotebookEditorService + @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this.initialContent = this.originalModel.getValue(); this._register(disposables); - this._register(this.modifiedModel.onDidChangeContent(e => { - this._mirrorEdits(e); - })); - this._register(toDisposable(() => { - this.clearCurrentEditLineDecoration(); + this._textModelChangeService = this._register(this.instantiationService.createInstance(ChatEditingTextModelChangeService, this.originalModel, this.modifiedModel, this.state)); + + this._register(this._textModelChangeService.onDidAcceptOrRejectAllHunks(action => { + this.revertMarkdownPreviewState(); + this._stateObs.set(action, undefined); })); - this._diffTrimWhitespace = observableConfigValue('diffEditor.ignoreTrimWhitespace', true, configService); - this._register(autorun(r => { - this._diffTrimWhitespace.read(r); - this._updateDiffInfoSeq(); + this._register(this._textModelChangeService.onDidUserEditModel(() => { + const didResetToOriginalContent = this.modifiedModel.getValue() === this.initialContent; + if (this._stateObs.get() === ModifiedFileEntryState.Modified && didResetToOriginalContent) { + this._stateObs.set(ModifiedFileEntryState.Rejected, undefined); + } })); + } public clearCurrentEditLineDecoration() { if (this.modifiedModel.isDisposed()) { return; } - this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, []); + this._textModelChangeService.clearCurrentEditLineDecoration(); } - - private _mirrorEdits(event: IModelContentChangedEvent) { - const edit = offsetEditFromContentChanges(event.changes); - - if (this._isEditFromUs) { - const e_sum = this._edit; - const e_ai = edit; - this._edit = e_sum.compose(e_ai); - - } else { - - // e_ai - // d0 ---------------> s0 - // | | - // | | - // | e_user_r | e_user - // | | - // | | - // v e_ai_r v - /// d1 ---------------> s1 - // - // d0 - document snapshot - // s0 - document - // e_ai - ai edits - // e_user - user edits - // - const e_ai = this._edit; - const e_user = edit; - - const e_user_r = e_user.tryRebase(e_ai.inverse(this.originalModel.getValue()), true); - - if (e_user_r === undefined) { - // user edits overlaps/conflicts with AI edits - this._edit = e_ai.compose(e_user); - } else { - const edits = offsetEditToEditOperations(e_user_r, this.originalModel); - this.originalModel.applyEdits(edits); - this._edit = e_ai.tryRebase(e_user_r); - } - - this._allEditsAreFromUs = false; - this._updateDiffInfoSeq(); - - const didResetToOriginalContent = this.modifiedModel.getValue() === this.initialContent; - const currentState = this._stateObs.get(); - switch (currentState) { - case ModifiedFileEntryState.Modified: - if (didResetToOriginalContent) { - this._stateObs.set(ModifiedFileEntryState.Rejected, undefined); - break; - } - } - - } - } - - acceptAgentEdits(textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): void { - const notebookEditor = this.notebookEditorService.retrieveExistingWidgetFromURI(this.notebookUri)?.value; - if (notebookEditor) { - const vm = notebookEditor.getCellByHandle(this.cell.handle); - vm?.updateEditState(CellEditState.Editing, 'chatEdit'); - } - - const ops = textEdits.map(TextEdit.asEditOperation); - const undoEdits = this._applyEdits(ops); - - const maxLineNumber = undoEdits.reduce((max, op) => Math.max(max, op.range.startLineNumber), 0); - - const newDecorations: IModelDeltaDecoration[] = [ - // decorate pending edit (region) - { - options: ChatEditingNotebookCellEntry._pendingEditDecorationOptions, - range: new Range(maxLineNumber + 1, 1, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) - } - ]; - - if (maxLineNumber > 0) { - // decorate last edit - newDecorations.push({ - options: ChatEditingNotebookCellEntry._lastEditDecorationOptions, - range: new Range(maxLineNumber, 1, maxLineNumber, Number.MAX_SAFE_INTEGER) - }); - } - - this._editDecorations = this.modifiedModel.deltaDecorations(this._editDecorations, newDecorations); - + async acceptAgentEdits(textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise { + const { maxLineNumber } = await this._textModelChangeService.acceptAgentEdits(this.modifiedModel.uri, textEdits, isLastEdits); transaction((tx) => { if (!isLastEdits) { this._stateObs.set(ModifiedFileEntryState.Modified, tx); - this._isCurrentlyBeingModifiedByObs.set(responseModel, tx); this._maxModifiedLineNumber.set(maxLineNumber, tx); } else { - this._resetEditsState(tx); - this._updateDiffInfoSeq(); this._maxModifiedLineNumber.set(0, tx); - this._editDecorationClear.schedule(); } }); } - scheduleEditDecorations() { - this._editDecorationClear.schedule(); - } - revertMarkdownPreviewState(): void { if (this.cell.cellKind !== CellKind.Markup) { return; @@ -245,119 +111,11 @@ export class ChatEditingNotebookCellEntry extends ObservableDisposable { } } - protected _resetEditsState(tx: ITransaction): void { - this._isCurrentlyBeingModifiedByObs.set(undefined, tx); - this._maxModifiedLineNumber.set(0, tx); - } - public async keep(change: DetailedLineRangeMapping): Promise { - return this._acceptHunk(change); - } - - private async _acceptHunk(change: DetailedLineRangeMapping): Promise { - this._isEditFromUs = true; - try { - if (!this._diffInfo.get().changes.filter(c => c.modified.equals(change.modified) && c.original.equals(change.original)).length) { - // diffInfo should have model version ids and check them (instead of the caller doing that) - return false; - } - const edits: ISingleEditOperation[] = []; - for (const edit of change.innerChanges ?? []) { - const newText = this.modifiedModel.getValueInRange(edit.modifiedRange); - edits.push(EditOperation.replace(edit.originalRange, newText)); - } - this.originalModel.pushEditOperations(null, edits, _ => null); - } - finally { - this._isEditFromUs = false; - } - await this._updateDiffInfoSeq(); - if (this._diffInfo.get().identical) { - this.revertMarkdownPreviewState(); - this._stateObs.set(ModifiedFileEntryState.Accepted, undefined); - } - return true; + return this._textModelChangeService.diffInfo.get().keep(change); } public async undo(change: DetailedLineRangeMapping): Promise { - return this._rejectHunk(change); - } - - private async _rejectHunk(change: DetailedLineRangeMapping): Promise { - this._isEditFromUs = true; - try { - if (!this._diffInfo.get().changes.includes(change)) { - return false; - } - const edits: ISingleEditOperation[] = []; - for (const edit of change.innerChanges ?? []) { - const newText = this.originalModel.getValueInRange(edit.originalRange); - edits.push(EditOperation.replace(edit.modifiedRange, newText)); - } - this.modifiedModel.pushEditOperations(null, edits, _ => null); - } finally { - this._isEditFromUs = false; - } - await this._updateDiffInfoSeq(); - if (this._diffInfo.get().identical) { - this.revertMarkdownPreviewState(); - this._stateObs.set(ModifiedFileEntryState.Rejected, undefined); - } - return true; - } - - private _applyEdits(edits: ISingleEditOperation[]) { - // make the actual edit - this._isEditFromUs = true; - try { - let result: ISingleEditOperation[] = []; - this.modifiedModel.pushEditOperations(null, edits, (undoEdits) => { - result = undoEdits; - return null; - }); - return result; - } finally { - this._isEditFromUs = false; - } - } - - private async _updateDiffInfoSeq() { - const myDiffOperationId = ++this._diffOperationIds; - await Promise.resolve(this._diffOperation); - if (this._diffOperationIds === myDiffOperationId) { - const thisDiffOperation = this._updateDiffInfo(); - this._diffOperation = thisDiffOperation; - await thisDiffOperation; - } - } - - private async _updateDiffInfo(): Promise { - - if (this.originalModel.isDisposed() || this.modifiedModel.isDisposed()) { - return; - } - - const docVersionNow = this.modifiedModel.getVersionId(); - const snapshotVersionNow = this.originalModel.getVersionId(); - - const ignoreTrimWhitespace = this._diffTrimWhitespace.get(); - - const diff = await this._editorWorkerService.computeDiff( - this.originalModel.uri, - this.modifiedModel.uri, - { ignoreTrimWhitespace, computeMoves: false, maxComputationTimeMs: 3000 }, - 'advanced' - ); - - if (this.originalModel.isDisposed() || this.modifiedModel.isDisposed()) { - return; - } - - // only update the diff if the documents didn't change in the meantime - if (this.modifiedModel.getVersionId() === docVersionNow && this.originalModel.getVersionId() === snapshotVersionNow) { - const diff2 = diff ?? nullDocumentDiff; - this._diffInfo.set(diff2, undefined); - this._edit = offsetEditFromLineRangeMapping(this.originalModel, this.modifiedModel, diff2.changes); - } + return this._textModelChangeService.diffInfo.get().undo(change); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts index b79dd102d43..885797824de 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts @@ -16,7 +16,6 @@ import { IWorkbenchContribution } from '../../../../../common/contributions.js'; import { INotebookService } from '../../../../notebook/common/notebookService.js'; import { IChatEditingService } from '../../../common/chatEditingService.js'; import { ChatEditingNotebookSnapshotScheme, deserializeSnapshot } from './chatEditingModifiedNotebookSnapshot.js'; -import { ChatEditingSession } from '../chatEditingSession.js'; export class ChatEditingNotebookFileSystemProviderContrib extends Disposable implements IWorkbenchContribution { @@ -89,7 +88,7 @@ export class ChatEditingNotebookFileSystemProvider implements IFileSystemProvide throw new Error('File not found, viewType not found'); } const session = this._chatEditingService.getEditingSession(queryData.sessionId); - if (!(session instanceof ChatEditingSession) || !queryData.requestId) { + if (!session || !queryData.requestId) { throw new Error('File not found, session not found'); } const snapshotEntry = session.getSnapshot(queryData.requestId, queryData.undoStop || undefined, resource); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts index 5ba3da96625..5015b2af65c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts @@ -31,7 +31,7 @@ import { IFileService } from '../../../../../platform/files/common/files.js'; import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; import { URI } from '../../../../../base/common/uri.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import { IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IPreferencesService } from '../../../../services/preferences/common/preferences.js'; import { IBrowserElementsService } from '../../../../services/browserElements/browser/browserElementsService.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 6dad621002c..e4ff9246db0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -20,7 +20,8 @@ import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; import { ChatAgentLocation } from '../common/constants.js'; import { ConfirmResult, IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; -import { shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from './actions/chatActions.js'; +import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; +import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; const ChatEditorIcon = registerIcon('chat-editor-label-icon', Codicon.commentDiscussion, nls.localize('chatEditorLabelIcon', 'Icon of the chat editor label.')); @@ -231,3 +232,50 @@ export class ChatEditorInputSerializer implements IEditorSerializer { } } } + +export async function showClearEditingSessionConfirmation(editingSession: IChatEditingSession, dialogService: IDialogService, options?: IClearEditingSessionConfirmationOptions): Promise { + const defaultPhrase = nls.localize('chat.startEditing.confirmation.pending.message.default1', "Starting a new chat will end your current edit session."); + const defaultTitle = nls.localize('chat.startEditing.confirmation.title', "Start new chat?"); + const phrase = options?.messageOverride ?? defaultPhrase; + const title = options?.titleOverride ?? defaultTitle; + + const currentEdits = editingSession.entries.get(); + const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); + + const { result } = await dialogService.prompt({ + title, + message: phrase + ' ' + nls.localize('chat.startEditing.confirmation.pending.message.2', "Do you want to keep pending edits to {0} files?", undecidedEdits.length), + type: 'info', + cancelButton: true, + buttons: [ + { + label: nls.localize('chat.startEditing.confirmation.acceptEdits', "Keep & Continue"), + run: async () => { + await editingSession.accept(); + return true; + } + }, + { + label: nls.localize('chat.startEditing.confirmation.discardEdits', "Undo & Continue"), + run: async () => { + await editingSession.reject(); + return true; + } + } + ], + }); + + return Boolean(result); +} + +export function shouldShowClearEditingSessionConfirmation(editingSession: IChatEditingSession): boolean { + const currentEdits = editingSession.entries.get(); + const currentEditCount = currentEdits.length; + + if (currentEditCount) { + const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); + return !!undecidedEdits.length; + } + + return false; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index 59f0cc16d5c..db18152a9be 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -41,8 +41,7 @@ import { ExplorerFolderContext } from '../../files/common/files.js'; import { IWorkspaceSymbol } from '../../search/common/search.js'; import { IChatContentInlineReference } from '../common/chatService.js'; import { IChatWidgetService } from './chat.js'; -import { hookUpSymbolAttachmentDragAndContextMenu } from './chatAttachmentWidgets.js'; -import { chatAttachmentResourceContextKey } from './chatContentParts/chatAttachmentsContentPart.js'; +import { chatAttachmentResourceContextKey, hookUpSymbolAttachmentDragAndContextMenu } from './chatAttachmentWidgets.js'; import { IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js'; type ContentRefData = diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index d6903664e1e..4281146eba3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -17,7 +17,6 @@ import { IAction } from '../../../../base/common/actions.js'; import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { logExecutionTime } from '../../../../base/common/decorators/logTime.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { HistoryNavigator2 } from '../../../../base/common/history.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; @@ -25,9 +24,11 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposab import { ResourceSet } from '../../../../base/common/map.js'; import { observableFromEvent } from '../../../../base/common/observable.js'; import { isMacintosh } from '../../../../base/common/platform.js'; +import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { assertType } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; +import { Schemas } from '../../../../base/common/network.js'; import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; @@ -76,34 +77,31 @@ import { IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatEditingSession } from '../common/chatEditingService.js'; import { ChatEntitlement, IChatEntitlementService } from '../common/chatEntitlementService.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatModel.js'; +import { IChatRequestVariableEntry, ChatRequestVariableSet, isPromptFileVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { ChatMode2, IChatMode, IChatModeService, isBuiltinChatMode, validateChatMode2 } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; -import { IChatVariablesService } from '../common/chatVariables.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatMode, isChatMode, validateChatMode } from '../common/constants.js'; import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; import { CancelAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSubmitAction, IChatExecuteActionContext, ToggleAgentModeActionId } from './actions/chatExecuteActions.js'; import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; -import { PromptInstructionsAttachmentsCollectionWidget } from './attachments/promptInstructions/promptInstructionsCollectionWidget.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; -import { toChatVariable } from './chatAttachmentModel/chatPromptAttachmentsCollection.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; import { IDisposableReference } from './chatContentParts/chatCollections.js'; import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js'; import { ChatDragAndDrop } from './chatDragAndDrop.js'; -import { ChatEditingRemoveAllFilesAction, ChatEditingShowChangesAction, ViewPreviousEditsAction } from './chatEditing/chatEditingActions.js'; +import { ChatEditingShowChangesAction, ViewPreviousEditsAction } from './chatEditing/chatEditingActions.js'; import { ChatFollowups } from './chatFollowups.js'; import { ChatSelectedTools } from './chatSelectedTools.js'; import { IChatViewState } from './chatWidget.js'; -import { ChatFileReference } from './contrib/chatDynamicVariables/chatFileReference.js'; import { ChatImplicitContext } from './contrib/chatImplicitContext.js'; import { ChatRelatedFiles } from './contrib/chatInputRelatedFilesContrib.js'; import { resizeImage } from './imageUtils.js'; import { IModelPickerDelegate, ModelPickerActionItem } from './modelPicker/modelPickerActionItem.js'; import { IModePickerDelegate, ModePickerActionItem } from './modelPicker/modePickerActionItem.js'; +import { PROMPT_LANGUAGE_ID } from '../common/promptSyntax/promptTypes.js'; const $ = dom.$; @@ -138,7 +136,6 @@ export interface IWorkingSetEntry { const GlobalLastChatModeKey = 'chat.lastChatMode'; export class ChatInputPart extends Disposable implements IHistoryNavigationWidget { - static readonly INPUT_SCHEME = 'chatSessionInput'; private static _counter = 0; private _onDidLoadInputState: Emitter; @@ -166,42 +163,20 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge readonly selectedToolsModel: ChatSelectedTools; - public async getAttachedAndImplicitContext(sessionId: string): Promise { - const contextArr = [...this.attachmentModel.attachments]; + public async getAttachedAndImplicitContext(sessionId: string): Promise { + + const contextArr = new ChatRequestVariableSet(); + + // get prompt file variables (instructions) and all rereferenced contents first + contextArr.add(... await this.attachmentModel.getPromptFileVariables()); + + // then add all other attachments (includes prompt file variables, but they will be ignored if already added) + contextArr.add(...this.attachmentModel.attachments); + if (this.implicitContext?.enabled && this.implicitContext.value) { - const implicitChatVariables = await this.implicitContext.toBaseEntries(); - contextArr.push(...implicitChatVariables); + contextArr.add(...implicitChatVariables); } - - // factor in nested file links of a prompt into the implicit context - const variables = this.variableService.getDynamicVariables(sessionId); - for (const variable of variables) { - if (!(variable instanceof ChatFileReference)) { - continue; - } - - // the usual URIs list of prompt instructions is `bottom-up`, therefore - // we do the same here - first add all child references to the list - contextArr.push( - ...variable.allValidReferences.map((link) => { - return toChatVariable(link, false); - }), - ); - } - - // prompt files may have nested child references to other prompt - // files that are resolved asynchronously, hence we need to wait - // for the entire prompt instruction tree to be processed - await logExecutionTime('instructions tree resolve', - this.promptInstructionsAttachmentsPart - .allSettled.bind(this.promptInstructionsAttachmentsPart), - this.logService.trace.bind(this.logService), - ); - - contextArr - .push(...this.promptInstructionsAttachmentsPart.chatAttachments); - return contextArr; } @@ -210,16 +185,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge */ get hasPromptFileAttachments(): boolean { // if prompt attached explicitly as a "prompt" attachment - if (this.promptInstructionsAttachmentsPart.hasInstructions) { - return true; - } - - if (this.implicitContext === undefined) { - return false; - } - - // if prompt attached as an implicit "current file" context - return (this.implicitContext.isPromptFile && this.implicitContext.enabled); + return this._attachmentModel.hasPromptFiles(PROMPT_LANGUAGE_ID); } private _indexOfLastAttachedContextDeletedWithKeyboard: number; @@ -364,12 +330,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private readonly getInputState: () => IChatInputState; - /** - * Child widget of prompt instruction attachments. - * See {@linkcode PromptInstructionsAttachmentsCollectionWidget}. - */ - private promptInstructionsAttachmentsPart: PromptInstructionsAttachmentsCollectionWidget; - /** * Number consumers holding the 'generating' lock. */ @@ -396,7 +356,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @ITextModelService private readonly textModelResolverService: ITextModelService, @IStorageService private readonly storageService: IStorageService, @ILabelService private readonly labelService: ILabelService, - @IChatVariablesService private readonly variableService: IChatVariablesService, @IChatAgentService private readonly agentService: IChatAgentService, @ISharedWebContentExtractorService private readonly sharedWebExtracterService: ISharedWebContentExtractorService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, @@ -431,7 +390,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidChangeCurrentChatMode = this._register(new Emitter()); this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event; this._currentMode = ChatMode2.Ask; - this.inputUri = URI.parse(`${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`); + this.inputUri = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); this._chatEditsActionsDisposables = this._register(new DisposableStore()); this._chatEditsDisposables = this._register(new DisposableStore()); this._attemptedWorkingSetEntriesCount = 0; @@ -468,24 +427,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } })); - this._chatEditsListPool = this._register(this.instantiationService.createInstance(CollapsibleListPool, this._onDidChangeVisibility.event, MenuId.ChatEditingWidgetModifiedFilesToolbar)); + this._chatEditsListPool = this._register(this.instantiationService.createInstance(CollapsibleListPool, this._onDidChangeVisibility.event, MenuId.ChatEditingWidgetModifiedFilesToolbar, { verticalScrollMode: ScrollbarVisibility.Visible })); this._hasFileAttachmentContextKey = ChatContextKeys.hasFileAttachments.bindTo(contextKeyService); - this.promptInstructionsAttachmentsPart = this._register( - instantiationService.createInstance( - PromptInstructionsAttachmentsCollectionWidget, - this.attachmentModel.promptInstructions, - this._contextResourceLabels, - ), - ); - - // trigger re-layout of chat input when number of instruction attachment changes - this._register(this.promptInstructionsAttachmentsPart.onAttachmentsChange(() => { - this._handleAttachedContextChange(); - this._onDidChangeHeight.fire(); - })); - this.initSelectedModel(); this._register(this.onDidChangeCurrentChatMode(() => this.accessibilityService.alert(this._currentMode.kind))); @@ -1298,7 +1243,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); const attachments = [...this.attachmentModel.attachments.entries()]; - const hasAttachments = Boolean(attachments.length) || Boolean(this.implicitContext?.value) || !this.promptInstructionsAttachmentsPart.empty; + const hasAttachments = Boolean(attachments.length) || Boolean(this.implicitContext?.value); dom.setVisibility(Boolean(hasAttachments || (this.addFilesToolbar && !this.addFilesToolbar.isEmpty())), this.attachmentsContainer); dom.setVisibility(hasAttachments, this.attachedContextContainer); if (!attachments.length) { @@ -1307,7 +1252,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this.promptFileAttached.set(this.hasPromptFileAttachments); - this.promptInstructionsAttachmentsPart.render(container); for (const [index, attachment] of attachments) { const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; @@ -1320,6 +1264,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge attachmentWidget = this.instantiationService.createInstance(ToolSetOrToolItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else if (resource && isNotebookOutputVariableEntry(attachment)) { attachmentWidget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + } else if (resource && isPromptFileVariableEntry(attachment)) { + attachmentWidget = this.instantiationService.createInstance(PromptFileAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) { attachmentWidget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else if (isImageVariableEntry(attachment)) { @@ -1497,7 +1443,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge arg: { sessionId: chatEditingSession.chatSessionId }, }, buttonConfigProvider: (action) => { - if (action.id === ChatEditingShowChangesAction.ID || action.id === ChatEditingRemoveAllFilesAction.ID || action.id === ViewPreviousEditsAction.Id) { + if (action.id === ChatEditingShowChangesAction.ID || action.id === ViewPreviousEditsAction.Id) { return { showIcon: true, showLabel: false, isSecondary: true }; } return undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f7e8a3d7012..9002a97a38c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -47,7 +47,7 @@ import { annotateSpecialMarkdownContent } from '../common/annotations.js'; import { checkModeOption } from '../common/chat.js'; import { IChatAgentMetadata } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatRequestVariableEntry, IChatTextEditGroup } from '../common/chatModel.js'; +import { IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatConfirmation, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop } from '../common/chatService.js'; import { IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatWorkingProgress, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; @@ -79,9 +79,12 @@ import { ChatMarkdownRenderer } from './chatMarkdownRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; import { ChatCodeBlockContentProvider, CodeBlockPart } from './codeBlockPart.js'; import { canceledName } from '../../../../base/common/errors.js'; +import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; const $ = dom.$; +const COPILOT_USERNAME = 'GitHub Copilot'; + interface IChatListItemTemplate { currentElement?: ChatTreeItem; /** @@ -168,7 +171,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('chat.experimental.renderMarkdownImmediately') === true; + const renderableResponse = annotateSpecialMarkdownContent(element.response.value); this.traceLayout('getNextProgressiveRenderContent', `Want to render ${data.numWordsToRender} at ${data.rate} words/s, counting...`); @@ -811,7 +821,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - if (model.uri.scheme === ChatInputPart.INPUT_SCHEME) { + if (model.uri.scheme === Schemas.vscodeChatInput) { return; } @@ -304,7 +304,7 @@ export class PasteTextProvider implements DocumentPasteEditProvider { ) { } async provideDocumentPasteEdits(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, _context: DocumentPasteContext, token: CancellationToken): Promise { - if (model.uri.scheme !== ChatInputPart.INPUT_SCHEME) { + if (model.uri.scheme !== Schemas.vscodeChatInput) { return; } const text = dataTransfer.get(Mimes.text); @@ -442,9 +442,10 @@ export class ChatPasteProvidersFeature extends Disposable { @ILogService logService: ILogService, ) { super(); - this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, instaService.createInstance(CopyAttachmentsProvider))); - this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteImageProvider(chatWidgetService, extensionService, fileService, environmentService, logService))); - this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteTextProvider(chatWidgetService, modelService))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: Schemas.vscodeChatInput, pattern: '*', hasAccessToAllModels: true }, instaService.createInstance(CopyAttachmentsProvider))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: Schemas.vscodeChatInput, pattern: '*', hasAccessToAllModels: true }, new PasteImageProvider(chatWidgetService, extensionService, fileService, environmentService, logService))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: Schemas.vscodeChatInput, pattern: '*', hasAccessToAllModels: true }, new PasteTextProvider(chatWidgetService, modelService))); + this._register(languageFeaturesService.documentPasteEditProvider.register('*', new CopyTextProvider())); this._register(languageFeaturesService.documentPasteEditProvider.register('*', new CopyTextProvider())); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts index 3b3d03913e6..f341a75c4c8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts @@ -151,6 +151,7 @@ export class ChatSelectedTools extends Disposable { asEnablementMap(): Map { const result = new Map(); + const map = this.entriesMap; const _set = (tool: IToolData, enabled: boolean) => { // ONLY disable a tool that isn't enabled yet @@ -160,10 +161,10 @@ export class ChatSelectedTools extends Disposable { } }; - for (const [item, enabled] of this.entriesMap) { + for (const [item, enabled] of map) { if (item instanceof ToolSet) { for (const tool of item.getTools()) { - _set(tool, enabled); + _set(tool, map.get(tool) ?? enabled); // tools from tool set can be explicitly set } } else { if (item.canBeReferencedInPrompt) { diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index d9ef1a37fee..6afc01477e5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -24,7 +24,7 @@ import { URI } from '../../../../base/common/uri.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize, localize2 } from '../../../../nls.js'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; @@ -58,7 +58,8 @@ import { IExtensionsWorkbenchService } from '../../extensions/common/extensions. import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../common/chatEntitlementService.js'; -import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestToolEntry, IChatRequestVariableData } from '../common/chatModel.js'; +import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../common/chatModel.js'; +import { IChatRequestToolEntry } from '../common/chatVariableEntries.js'; import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; import { IChatProgress, IChatService } from '../common/chatService.js'; import { ChatAgentLocation, ChatConfiguration, ChatMode, validateChatMode } from '../common/constants.js'; @@ -389,7 +390,8 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { private async doInvokeWithSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise { this.telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'chat' }); - const requestModel = chatWidgetService.getWidgetBySessionId(request.sessionId)?.viewModel?.model.getRequests().at(-1); + const widget = chatWidgetService.getWidgetBySessionId(request.sessionId); + const requestModel = widget?.viewModel?.model.getRequests().at(-1); const setupListener = Event.runAndSubscribe(this.controller.value.onDidChange, (() => { switch (this.controller.value.step) { @@ -421,10 +423,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { if (typeof result?.success === 'boolean') { if (result.success) { if (result.dialogSkipped) { - progress({ - kind: 'markdownContent', - content: new MarkdownString(localize('copilotSetupSuccess', "Copilot setup finished successfully.")) - }); + widget?.clear(); // make room for the Chat welcome experience } else if (requestModel) { let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Copilot agent... newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Copilot tools @@ -988,7 +987,6 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr this.registerSetupAgents(context, controller); this.registerActions(context, requests, controller); - this.registerMenus(); this.registerUrlLinkHandler(); } @@ -1294,38 +1292,6 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr registerAction2(EnableOveragesAction); } - private registerMenus(): void { - const menuContext = ContextKeyExpr.and( - // TODO@bpasero this needs to be revisited when the Copilot - // extension contributes this OOTB where this menu is also - // defined. - ChatContextKeys.Setup.installed.negate(), - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.disabled.negate() - ); - - MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - submenu: MenuId.ChatExplorerMenu, - group: '5_copilot', - title: localize('title4', "Copilot"), - when: menuContext - }); - - MenuRegistry.appendMenuItem(MenuId.EditorContext, { - submenu: MenuId.ChatTextEditorMenu, - group: '1_copilot', - title: localize('title4', "Copilot"), - when: menuContext - }); - - MenuRegistry.appendMenuItem(MenuId.TerminalInstanceContext, { - submenu: MenuId.ChatTerminalMenu, - group: '2_copilot', - title: localize('title4', "Copilot"), - when: menuContext - }); - } - private registerUrlLinkHandler(): void { this._register(ExtensionUrlHandlerOverrideRegistry.registerHandler({ canHandleURL: url => { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index ec29789b0f1..acb42d186d5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -34,7 +34,7 @@ import { ServiceCollection } from '../../../../platform/instantiation/common/ser import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js'; -import { PromptsType } from '../../../../platform/prompts/common/prompts.js'; +import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; @@ -43,7 +43,7 @@ import { checkModeOption } from '../common/chat.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, ModifiedFileEntryState } from '../common/chatEditingService.js'; -import { ChatPauseState, IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; +import { ChatPauseState, IChatModel, IChatResponseModel } from '../common/chatModel.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestSlashPromptPart, ChatRequestToolPart, ChatRequestToolSetPart, chatSubcommandLeader, formatChatQuestion, IParsedChatRequest } from '../common/chatParserTypes.js'; import { ChatRequestParser } from '../common/chatRequestParser.js'; import { IChatLocationData, IChatSendRequestOptions, IChatService } from '../common/chatService.js'; @@ -54,12 +54,11 @@ import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js' import { ChatAgentLocation, ChatMode } from '../common/constants.js'; import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/languageModelToolsService.js'; import { type TPromptMetadata } from '../common/promptSyntax/parsers/promptHeader/promptHeader.js'; -import { IMetadata, IPromptsService } from '../common/promptSyntax/service/types.js'; +import { IMetadata, IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { handleModeSwitch } from './actions/chatActions.js'; import { ChatTreeItem, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from './chat.js'; import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; -import { addPromptFileChatVariable, isPromptFileChatVariable } from './chatAttachmentModel/chatPromptAttachmentsCollection.js'; import { ChatInputPart, IChatInputStyles } from './chatInputPart.js'; import { ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from './chatListRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; @@ -67,6 +66,9 @@ import './media/chat.css'; import './media/chatAgentHover.css'; import './media/chatViewWelcome.css'; import { ChatViewWelcomePart } from './viewsWelcome/chatViewWelcomeController.js'; +import { MicrotaskDelay } from '../../../../base/common/symbols.js'; +import { IChatRequestVariableEntry, ChatRequestVariableSet as ChatRequestVariableSet, isPromptFileVariableEntry, toPromptFileVariableEntry } from '../common/chatVariableEntries.js'; +import { PromptsConfig } from '../common/promptSyntax/config/config.js'; const $ = dom.$; @@ -1077,7 +1079,9 @@ export class ChatWidget extends Disposable implements IChatWidget { this.container.setAttribute('data-session-id', model.sessionId); this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection); - this.viewModelDisposables.add(Event.runAndSubscribe(Event.accumulate(this.viewModel.onDidChange, 0), (events => { + const renderImmediately = this.configurationService.getValue('chat.experimental.renderMarkdownImmediately') === true; + const delay = renderImmediately ? MicrotaskDelay : 0; + this.viewModelDisposables.add(Event.runAndSubscribe(Event.accumulate(this.viewModel.onDidChange, delay), (events => { if (!this.viewModel) { return; } @@ -1201,16 +1205,16 @@ export class ChatWidget extends Disposable implements IChatWidget { return inputState; } - private _findPromptFileInContext(attachedContext: IChatRequestVariableEntry[]): URI | undefined { - for (const item of attachedContext) { - if (isPromptFileChatVariable(item) && item.isRoot) { + private _findPromptFileInContext(attachedContext: ChatRequestVariableSet): URI | undefined { + for (const item of attachedContext.asArray()) { + if (isPromptFileVariableEntry(item) && item.isRoot) { return IChatRequestVariableEntry.toUri(item); } } return undefined; } - private async _applyPromptFileIfSet(requestInput: { input: string; attachedContext: IChatRequestVariableEntry[] }): Promise { + private async _applyPromptFileIfSet(requestInput: { input: string; attachedContext: ChatRequestVariableSet }): Promise { let metadata: IMetadata | undefined; @@ -1220,7 +1224,8 @@ export class ChatWidget extends Disposable implements IChatWidget { metadata = await this.promptsService.resolvePromptSlashCommand(agentSlashPromptPart.slashPromptCommand); if (metadata) { // add the prompt file to the context, but not sticky - addPromptFileChatVariable(requestInput.attachedContext, metadata.uri); + requestInput.attachedContext.add(toPromptFileVariableEntry(metadata.uri, true)); + // remove the slash command from the input requestInput.input = this.parsedInput.parts.filter(part => !(part instanceof ChatRequestSlashPromptPart)).map(part => part.text).join('').trim(); } @@ -1277,8 +1282,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const isUserQuery = !query; - const { promptInstructions } = this.inputPart.attachmentModel; - const instructionsEnabled = promptInstructions.featureEnabled; + const instructionsEnabled = PromptsConfig.enabled(this.configurationService); if (instructionsEnabled) { await this._applyPromptFileIfSet(requestInputs); await this.autoAttachInstructions(requestInputs.attachedContext); @@ -1286,7 +1290,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (this.viewOptions.enableWorkingSet !== undefined && this.input.currentMode === ChatMode.Edit && !this.chatService.edits2Enabled) { const uniqueWorkingSetEntries = new ResourceSet(); // NOTE: this is used for bookkeeping so the UI can avoid rendering references in the UI that are already shown in the working set - const editingSessionAttachedContext: IChatRequestVariableEntry[] = requestInputs.attachedContext; + const editingSessionAttachedContext: ChatRequestVariableSet = requestInputs.attachedContext; // Collect file variables from previous requests before sending the request const previousRequests = this.viewModel.model.getRequests(); @@ -1295,7 +1299,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (URI.isUri(variable.value) && variable.kind === 'file') { const uri = variable.value; if (!uniqueWorkingSetEntries.has(uri)) { - editingSessionAttachedContext.push(variable); + editingSessionAttachedContext.add(variable); uniqueWorkingSetEntries.add(variable.value); } } @@ -1326,7 +1330,7 @@ export class ChatWidget extends Disposable implements IChatWidget { location: this.location, locationData: this._location.resolveData?.(), parserContext: { selectedAgent: this._lastSelectedAgent, mode: this.inputPart.currentMode }, - attachedContext: requestInputs.attachedContext, + attachedContext: requestInputs.attachedContext.asArray(), noCommandDetection: options?.noCommandDetection, userSelectedTools: this.getUserSelectedTools(), modeInstructions: this.input.currentMode2.body @@ -1615,24 +1619,17 @@ export class ChatWidget extends Disposable implements IChatWidget { * match file references in the attached context and then attaches * such instructions to the context. */ - private async autoAttachInstructions( - attachedContext: IChatRequestVariableEntry[], - ): Promise { + private async autoAttachInstructions(attachedContext: ChatRequestVariableSet): Promise { - const variableUris = attachedContext - .map(IChatRequestVariableEntry.toUri) - .filter(isDefined); + const variableUris = attachedContext.asArray().map(IChatRequestVariableEntry.toUri).filter(isDefined); - const automaticInstructions = await this.promptsService - .findInstructionFilesFor(variableUris); + const automaticInstructions = await this.promptsService.findInstructionFilesFor(variableUris); // add instructions to the final context list - automaticInstructions.forEach(instruction => addPromptFileChatVariable(attachedContext, instruction)); + attachedContext.add(...automaticInstructions.map(instruction => toPromptFileVariableEntry(instruction, true))); // add to attached list to make the instructions sticky - this.inputPart - .attachmentModel - .promptInstructions.add(automaticInstructions); + this.inputPart.attachmentModel.addPromptFiles(automaticInstructions); } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 8c3f93517f1..4ebdeae3f35 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -12,28 +12,22 @@ import { IDecorationOptions } from '../../../../../editor/common/editorCommon.js import { Command, isLocation } from '../../../../../editor/common/languages.js'; import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; -import { PromptsConfig } from '../../../../../platform/prompts/common/config.js'; import { IChatRequestVariableValue, IDynamicVariable } from '../../common/chatVariables.js'; import { IChatWidget } from '../chat.js'; -import { ChatWidget, IChatWidgetContrib } from '../chatWidget.js'; -import { ChatFileReference } from './chatDynamicVariables/chatFileReference.js'; +import { IChatWidgetContrib } from '../chatWidget.js'; export const dynamicVariableDecorationType = 'chat-dynamic-variable'; -/** - * Type of dynamic variables. Can be either a file reference or - * another dynamic variable (e.g., a `#sym`, `#kb`, etc.). - */ -type TDynamicVariable = IDynamicVariable | ChatFileReference; + export class ChatDynamicVariableModel extends Disposable implements IChatWidgetContrib { public static readonly ID = 'chatDynamicVariableModel'; - private _variables: TDynamicVariable[] = []; - get variables(): ReadonlyArray { + private _variables: IDynamicVariable[] = []; + + get variables(): ReadonlyArray { return [...this._variables]; } @@ -46,18 +40,16 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC constructor( private readonly widget: IChatWidget, @ILabelService private readonly labelService: ILabelService, - @IConfigurationService private readonly configService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); this._register(widget.inputEditor.onDidChangeModelContent(e => { - const removed: TDynamicVariable[] = []; + const removed: IDynamicVariable[] = []; let didChange = false; // Don't mutate entries in _variables, since they will be returned from the getter - this._variables = coalesce(this._variables.map((ref, idx): TDynamicVariable | null => { + this._variables = coalesce(this._variables.map((ref, idx): IDynamicVariable | null => { const model = widget.inputEditor.getModel(); if (!model) { @@ -94,12 +86,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC didChange = true; - if (ref instanceof ChatFileReference) { - ref.range = newRange; - return ref; - } else { - return { ...ref, range: newRange }; - } + return { ...ref, range: newRange }; })); // cleanup disposable variables @@ -114,15 +101,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC } getInputState(): any { - return this.variables - .map((variable: TDynamicVariable) => { - // return underlying `IDynamicVariable` object for file references - if (variable instanceof ChatFileReference) { - return variable.reference; - } - - return variable; - }); + return this.variables; } setInputState(s: any): void { @@ -143,26 +122,9 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC } addReference(ref: IDynamicVariable): void { - // use `ChatFileReference` for file references and `IDynamicVariable` for other variables - const promptSnippetsEnabled = PromptsConfig.enabled(this.configService); - const variable = (ref.id === 'vscode.file' && promptSnippetsEnabled) - ? this.instantiationService.createInstance(ChatFileReference, ref) - : ref; - - this._variables.push(variable); + this._variables.push(ref); this.updateDecorations(); this.widget.refreshParsedInput(); - - // if the `prompt snippets` feature is enabled, and file is a `prompt snippet`, - // start resolving nested file references immediately and subscribe to updates - if (variable instanceof ChatFileReference && variable.isPromptFile) { - // subscribe to variable changes - variable.onUpdate(() => { - this.updateDecorations(); - }); - // start resolving the file references - variable.start(); - } } private updateDecorations(): void { @@ -221,9 +183,6 @@ function isDynamicVariable(obj: any): obj is IDynamicVariable { 'data' in obj; } -ChatWidget.CONTRIBS.push(ChatDynamicVariableModel); - - export interface IAddDynamicVariableContext { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts deleted file mode 100644 index a7e4d40fa61..00000000000 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../../base/common/uri.js'; -import { assert } from '../../../../../../base/common/assert.js'; -import { IDynamicVariable } from '../../../common/chatVariables.js'; -import { IRange } from '../../../../../../editor/common/core/range.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { FilePromptParser } from '../../../common/promptSyntax/parsers/filePromptParser.js'; -import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; - -/** - * A wrapper class for an `IDynamicVariable` object that that adds functionality - * to parse nested file references of this variable. - * See {@link FilePromptParser} for details. - */ -export class ChatFileReference extends FilePromptParser implements IDynamicVariable { - /** - * @throws if the `data` reference is no an instance of `URI`. - */ - constructor( - public readonly reference: IDynamicVariable, - @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService workspaceService: IWorkspaceContextService, - @ILogService logService: ILogService, - ) { - const { data } = reference; - - assert( - data instanceof URI, - `Variable data must be an URI, got '${data}'.`, - ); - - super(data, {}, instantiationService, workspaceService, logService); - } - - /** - * Note! below are the getters that simply forward to the underlying `IDynamicVariable` object; - * while we could implement the logic generically using the `Proxy` class here, it's hard - * to make Typescript to recognize this generic implementation correctly - */ - - public get id() { - return this.reference.id; - } - - public get range() { - return this.reference.range; - } - - public set range(range: IRange) { - this.reference.range = range; - } - - public get data(): URI { - return this.uri; - } - - public get isFile() { - return this.reference.isFile; - } - - public get fullName() { - return this.reference.fullName; - } - - public get icon() { - return this.reference.icon; - } - - public get modelDescription() { - return this.reference.modelDescription; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index ef5a6cfc78a..6ede7942f8f 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -13,22 +13,18 @@ import { URI } from '../../../../../base/common/uri.js'; import { getCodeEditor, ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { Location } from '../../../../../editor/common/languages.js'; -import { IModelService } from '../../../../../editor/common/services/model.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { ILogService } from '../../../../../platform/log/common/log.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { EditorsOrder } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { getNotebookEditorFromEditorPane, INotebookEditor } from '../../../notebook/browser/notebookBrowser.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { IChatRequestFileEntry, IChatRequestImplicitVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestImplicitVariableEntry, IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatService } from '../../common/chatService.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { ILanguageModelIgnoredFilesService } from '../../common/ignoredFiles.js'; -import { PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/constants.js'; -import { IPromptsService, TSharedPrompt } from '../../common/promptSyntax/service/types.js'; +import { getPromptsTypeForLanguageId } from '../../common/promptSyntax/promptTypes.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; -import { toChatVariable } from '../chatAttachmentModel/chatPromptAttachmentsCollection.js'; export class ChatImplicitContextContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'chat.implicitContext'; @@ -156,6 +152,7 @@ export class ChatImplicitContextContribution extends Disposable implements IWork let languageId: string | undefined; if (model) { newValue = model.uri; + languageId = model.getLanguageId(); } const notebookEditor = this.findActiveNotebookEditor(); @@ -175,6 +172,8 @@ export class ChatImplicitContextContribution extends Disposable implements IWork return; } + const isPromptFile = languageId && getPromptsTypeForLanguageId(languageId) !== undefined; + const widgets = updateWidget ? [updateWidget] : [...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel), ...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Editor)]; for (const widget of widgets) { if (!widget.input.implicitContext) { @@ -182,11 +181,9 @@ export class ChatImplicitContextContribution extends Disposable implements IWork } const setting = this._implicitContextEnablement[widget.location]; const isFirstInteraction = widget.viewModel?.getItems().length === 0; - if (setting === 'first' && !isFirstInteraction) { - widget.input.implicitContext.setValue(undefined, false, undefined); - } else if (setting === 'always' || setting === 'first' && isFirstInteraction) { + if ((setting === 'always' || setting === 'first' && isFirstInteraction) && !isPromptFile) { // disable implicit context for prompt files widget.input.implicitContext.setValue(newValue, isSelection, languageId); - } else if (setting === 'never') { + } else { widget.input.implicitContext.setValue(undefined, false, undefined); } } @@ -194,19 +191,8 @@ export class ChatImplicitContextContribution extends Disposable implements IWork } export class ChatImplicitContext extends Disposable implements IChatRequestImplicitVariableEntry { - /** - * If the implicit context references a prompt file, this field - * holds a reference to an associated prompt parser instance. - */ - private prompt: TSharedPrompt | undefined; get id() { - if (this.prompt !== undefined) { - const variable = toChatVariable(this.prompt, true); - - return variable.id; - } - if (URI.isUri(this.value)) { return 'vscode.implicit.file'; } else if (this.value) { @@ -221,12 +207,6 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli } get name(): string { - if (this.prompt !== undefined) { - const variable = toChatVariable(this.prompt, true); - - return variable.name; - } - if (URI.isUri(this.value)) { return `file:${basename(this.value)}`; } else if (this.value) { @@ -239,12 +219,6 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli readonly kind = 'implicit'; get modelDescription(): string { - if (this.prompt !== undefined) { - const variable = toChatVariable(this.prompt, true); - - return variable.modelDescription; - } - if (URI.isUri(this.value)) { return `User's active file`; } else if (this._isSelection) { @@ -279,96 +253,20 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli this._onDidChangeValue.fire(); } - constructor( - @IPromptsService private readonly promptsService: IPromptsService, - @IModelService private readonly modelService: IModelService, - @ILogService private readonly logService: ILogService, - ) { - super(); - } - setValue(value: Location | URI | undefined, isSelection: boolean, languageId?: string): void { this._value = value; this._isSelection = isSelection; - - // remove and dispose existent prompt parser instance - this.removePrompt(); - // if language ID is a 'prompt' language, create a prompt parser instance - if (value && (languageId === PROMPT_LANGUAGE_ID)) { - this.addPrompt(value); - } this._onDidChangeValue.fire(); } - public async toBaseEntries(): Promise { - // chat variable for non-prompt file attachment - if (this.prompt === undefined) { - return [{ - kind: 'file', - id: this.id, - name: this.name, - value: this.value, - modelDescription: this.modelDescription, - }]; - - } - - // prompt can have any number of nested references, hence - // collect all of valid ones and return the entire list - await this.prompt.allSettled(); - return [ - // add all valid child references in the prompt - ...this.prompt.allValidReferences.map((link) => { - return toChatVariable(link, false); - }), - // and then the root prompt reference itself - toChatVariable({ - uri: this.prompt.uri, - // the attached file must have been a prompt file therefore - // we force that assumption here; this makes sure that prompts - // in untitled documents can be also attached to the chat input - isPromptFile: true, - }, true), - ]; + public async toBaseEntries(): Promise { + return [{ + kind: 'file', + id: this.id, + name: this.name, + value: this.value, + modelDescription: this.modelDescription, + }]; } - /** - * Whether the implicit context references a prompt file. - */ - public get isPromptFile() { - return (this.prompt !== undefined); - } - - /** - * Add prompt parser instance for the provided value. - */ - private addPrompt( - value: URI | Location, - ): void { - const uri = URI.isUri(value) - ? value - : value.uri; - - const model = this.modelService.getModel(uri); - const modelExists = (model !== null); - if ((modelExists === false) || model.isDisposed()) { - return this.logService.warn( - `cannot create prompt parser instance for ${uri.path} (model exists: ${modelExists})`, - ); - } - - this.prompt = this.promptsService.getSyntaxParserFor(model); - } - - /** - * Remove and dispose prompt parser instance. - */ - private removePrompt(): void { - delete this.prompt; - } - - public override dispose(): void { - this.removePrompt(); - super.dispose(); - } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 8c765ee7f36..13067fe7199 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -46,16 +46,16 @@ import { IMcpPrompt, IMcpPromptMessage, IMcpServer, IMcpService, McpResourceURI import { searchFilesAndFolders } from '../../../search/browser/chatContributions.js'; import { IChatAgentData, IChatAgentNameService, IChatAgentService, getFullyQualifiedId } from '../../common/chatAgents.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { getAttachableImageExtension, IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { getAttachableImageExtension } from '../../common/chatModel.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from '../../common/chatParserTypes.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IDynamicVariable } from '../../common/chatVariables.js'; import { ChatAgentLocation, ChatMode } from '../../common/constants.js'; import { ToolSet } from '../../common/languageModelToolsService.js'; -import { IPromptsService } from '../../common/promptSyntax/service/types.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { ChatSubmitAction } from '../actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; -import { ChatInputPart } from '../chatInputPart.js'; import { ChatDynamicVariableModel } from './chatDynamicVariables.js'; class SlashCommandCompletions extends Disposable { @@ -68,7 +68,7 @@ class SlashCommandCompletions extends Disposable { ) { super(); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'globalSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -115,7 +115,7 @@ class SlashCommandCompletions extends Disposable { }; } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'globalSlashCommandsAt', triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -156,7 +156,7 @@ class SlashCommandCompletions extends Disposable { }; } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'promptSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -204,7 +204,7 @@ class SlashCommandCompletions extends Disposable { } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'mcpPromptSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { @@ -306,9 +306,9 @@ class AgentCompletions extends Disposable { }; } }; - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, subCommandProvider)); + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, subCommandProvider)); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentAndSubcommand', triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -398,7 +398,7 @@ class AgentCompletions extends Disposable { } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentAndSubcommand', triggerCharacters: [chatSubcommandLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -456,7 +456,7 @@ class AgentCompletions extends Disposable { } })); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'installChatExtensions', triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -841,7 +841,7 @@ class BuiltinDynamicCompletions extends Disposable { } private registerVariableCompletions(debugName: string, provider: (details: IVariableCompletionsDetails, token: CancellationToken) => ProviderResult, wordPattern: RegExp = BuiltinDynamicCompletions.VariableNameDef) { - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: `chatVarCompletions-${debugName}`, triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken) => { @@ -1094,7 +1094,7 @@ class ToolCompletions extends Disposable { ) { super(); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatVariables', triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts b/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts index f030477d0df..8e18015fc20 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/screenshot.ts @@ -5,7 +5,7 @@ import { VSBuffer } from '../../../../../base/common/buffer.js'; import { localize } from '../../../../../nls.js'; -import { IChatRequestVariableEntry } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; export const ScreenshotVariableId = 'screenshot-focused-window'; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 2c215b12237..d1651847dc4 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -33,7 +33,9 @@ margin-bottom: 8px; } -.interactive-item-container .header.hidden { +.interactive-item-container .header.hidden, +.interactive-item-container .header .avatar-container.hidden, +.interactive-item-container .header .username.hidden { display: none; } @@ -1121,66 +1123,22 @@ have to be updated for changes to the rules above, or to support more deeply nes /** * Styles for the `reusable prompts` attachment widget. */ -.chat-attached-context .chat-prompt-attachment { - display: flex; - gap: 4px; -} -.chat-attached-context .chat-prompt-attachment .codicon { - color: inherit; - text-decoration: none; -} -.chat-attached-context .chat-prompt-attachment .chat-implicit-hint { +.chat-attached-context-attachment .prompt-type { opacity: 0.7; font-size: .9em; - margin-top: -0.5px; + margin-left: 0.5em; } -.chat-attached-context .chat-prompt-attachment.warning { +.chat-attached-context-attachment.warning { color: var(--vscode-notificationsWarningIcon-foreground); } -.chat-attached-context .chat-prompt-attachment.error { +.chat-attached-context-attachment.error { color: var(--vscode-notificationsErrorIcon-foreground); } -.chat-attached-context .chat-prompt-attachment.disabled { - border-style: dashed; - opacity: 0.75; -} -.chat-attached-context .chat-prompt-attachment:focus .monaco-button { - border-color: var(--vscode-focusBorder); -} -.chat-attached-context .chat-prompt-attachment .monaco-icon-label-container { - margin-top: -0.1em; -} -.chat-attached-context .chat-prompt-attachment.warning.implicit { - border: 1px solid currentColor; -} -.chat-attached-context .chat-prompt-attachment.implicit .monaco-button { - padding-left: 2px; - padding-right: 2px; -} -/* - * If in one of the non-normal states, make sure the `main icon` of - * the component has the same color as the component itself - */ -.chat-attached-context .chat-prompt-attachment.error .monaco-icon-label::before, -.chat-attached-context .chat-prompt-attachment.warning .monaco-icon-label::before, -.chat-attached-context .chat-prompt-attachment.disabled .monaco-icon-label::before { - color: inherit; -} -.chat-attached-context .chat-prompt-attachment.disabled .monaco-icon-label::before { - font-style: italic; -} -.chat-attached-context .chat-prompt-attachment.disabled:hover { + +.chat-attached-context-attachment .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-suffix-container > .label-suffix { + color: var(--vscode-peekViewTitleDescription-foreground); opacity: 1; } -.chat-attached-context .chat-prompt-attachment.disabled .chat-implicit-hint, -.chat-attached-context .chat-prompt-attachment.disabled .label-name { - font-style: italic; - text-decoration: line-through; -} -.chat-attached-context .chat-prompt-attachment.disabled:focus { - outline: none; - border-color: var(--vscode-focusBorder); -} .chat-notification-widget .chat-warning-codicon .codicon-warning, .chat-quota-error-widget .codicon-warning { @@ -1624,6 +1582,7 @@ have to be updated for changes to the rules above, or to support more deeply nes font-size: 12px; color: var(--vscode-descriptionForeground); user-select: none; + border-left: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent)); code { font-size: 11px; @@ -1947,7 +1906,8 @@ have to be updated for changes to the rules above, or to support more deeply nes box-shadow: none; } - .interactive-request .header.header-disabled { + .interactive-request .header.header-disabled, + .request-hover.has-no-actions { display: none !important; } .request-hover { @@ -1968,19 +1928,19 @@ have to be updated for changes to the rules above, or to support more deeply nes height: 22px; } - .request-hover .actions-container .action-label.codicon-x { + .request-hover .actions-container .action-label.codicon-discard { margin-top: 4px; padding: 3px 3px; } - .request-hover.has-no-actions { - display: none !important; - } - .interactive-list > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { overflow: visible !important; } + .interactive-list > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.monaco-list-row.focused.request { + outline: none !important; + } + div[data-index="0"] .monaco-tl-contents { .interactive-item-container.interactive-request { padding-top: 19px; @@ -1991,7 +1951,6 @@ have to be updated for changes to the rules above, or to support more deeply nes } } - .interactive-list > .monaco-list:focus > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused.request { outline: none !important; diff --git a/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css b/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css index d7d5ac7f77b..8e489f04bc6 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css @@ -58,11 +58,11 @@ span.label-detail { span.label-added { font-weight: bold; padding-left: 4px; - color: var(--vscode-editorGutter-addedBackground); + color: var(--vscode-chat-linesAddedForeground); } span.label-removed { font-weight: bold; padding-left: 4px; - color: var(--vscode-editorGutter-deletedBackground); + color: var(--vscode-chat-linesRemovedForeground); } diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts index 83fe032ddbd..d9d7dedff14 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts @@ -19,7 +19,7 @@ import { IKeybindingService } from '../../../../../platform/keybinding/common/ke import { IChatAgentService } from '../../common/chatAgents.js'; import { IChatMode, IChatModeService } from '../../common/chatModes.js'; import { ChatAgentLocation, ChatMode, modeToString } from '../../common/constants.js'; -import { IPromptsService } from '../../common/promptSyntax/service/types.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { getOpenChatActionIdForMode } from '../actions/chatActions.js'; import { IToggleChatModeArgs } from '../actions/chatExecuteActions.js'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts similarity index 64% rename from src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts index 52432186bc9..93ccc2e8544 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts @@ -3,36 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChatViewId, IChatWidget, IChatWidgetService } from '../../chat.js'; -import { CHAT_CATEGORY } from '../chatActions.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { localize, localize2 } from '../../../../../../nls.js'; -import { ChatContextKeys } from '../../../common/chatContextKeys.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { IPromptsService } from '../../../common/promptSyntax/service/types.js'; -import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js'; -import { IViewsService } from '../../../../../services/views/common/viewsService.js'; -import { PromptFilePickers } from './dialogs/askToSelectPrompt/promptFilePickers.js'; -import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; -import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { attachInstructionsFiles, IAttachOptions } from './dialogs/askToSelectPrompt/utils/attachInstructions.js'; -import { ChatContextPick, IChatContextPickerItem, IChatContextPickerPickItem } from '../../chatContextPickService.js'; -import { IQuickPickSeparator } from '../../../../../../platform/quickinput/common/quickInput.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { getCleanPromptName, PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; -import { compare } from '../../../../../../base/common/strings.js'; -import { ILabelService } from '../../../../../../platform/label/common/label.js'; -import { dirname } from '../../../../../../base/common/resources.js'; -import { IPromptFileVariableEntry } from '../../../common/chatModel.js'; -import { KeyMod, KeyCode } from '../../../../../../base/common/keyCodes.js'; -import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { ICodeEditorService } from '../../../../../../editor/browser/services/codeEditorService.js'; -import { INSTRUCTIONS_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; +import { ChatViewId, IChatWidget, IChatWidgetService, showChatView } from '../chat.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; +import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { PromptFilePickers } from './pickers/promptFilePickers.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPicker } from '../chatContextPickService.js'; +import { IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { getCleanPromptName } from '../../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_LANGUAGE_ID, PromptsType } from '../../common/promptSyntax/promptTypes.js'; +import { compare } from '../../../../../base/common/strings.js'; +import { ILabelService } from '../../../../../platform/label/common/label.js'; +import { dirname } from '../../../../../base/common/resources.js'; +import { IPromptFileVariableEntry, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js'; +import { KeyMod, KeyCode } from '../../../../../base/common/keyCodes.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; /** * Action ID for the `Attach Instruction` action. @@ -100,7 +98,6 @@ class AttachInstructionsAction extends Action2 { options?: IAttachInstructionsActionOptions, ): Promise { const viewsService = accessor.get(IViewsService); - const commandService = accessor.get(ICommandService); const instaService = accessor.get(IInstantiationService); if (!options) { @@ -114,25 +111,15 @@ class AttachInstructionsAction extends Action2 { const { skipSelectionDialog, resource } = options; - const attachOptions: IAttachOptions = { - widget: options.widget, - viewsService, - commandService, - }; - if (skipSelectionDialog) { - assertDefined( - resource, - 'Resource must be defined when skipping prompt selection dialog.', - ); - - const widget = await attachInstructionsFiles( - [resource], - attachOptions, - ); + const widget = options.widget ?? (await showChatView(viewsService)); + if (!widget) { + return; + } + if (skipSelectionDialog && resource) { + widget.attachmentModel.addPromptFiles([resource]); widget.focusInput(); - return; } @@ -144,10 +131,7 @@ class AttachInstructionsAction extends Action2 { const result = await pickers.selectPromptFile({ resource, placeholder, type: PromptsType.instructions }); if (result !== undefined) { - const widget = await attachInstructionsFiles( - [result.promptFile], - attachOptions, - ); + widget.attachmentModel.addPromptFiles([result.promptFile]); widget.focusInput(); } } @@ -157,7 +141,7 @@ class ManageInstructionsFilesAction extends Action2 { constructor() { super({ id: CONFIGURE_INSTRUCTIONS_ACTION_ID, - title: localize2('configure-instructions', "Configure Instructions"), + title: localize2('configure-instructions', "Configure Instructions..."), icon: Codicon.bookmark, f1: true, precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), @@ -214,22 +198,22 @@ function getFocusedChatWidget(accessor: ServicesAccessor): IChatWidget | undefin /** * Gets `URI` of a instructions file open in an active editor instance, if any. */ -const getActiveInstructionsFileUri = (accessor: ServicesAccessor): URI | undefined => { +function getActiveInstructionsFileUri(accessor: ServicesAccessor): URI | undefined { const codeEditorService = accessor.get(ICodeEditorService); const model = codeEditorService.getActiveCodeEditor()?.getModel(); if (model?.getLanguageId() === INSTRUCTIONS_LANGUAGE_ID) { return model.uri; } return undefined; -}; +} /** * Helper to register the `Attach Prompt` action. */ -export const registerAttachPromptActions = () => { +export function registerAttachPromptActions(): void { registerAction2(AttachInstructionsAction); registerAction2(ManageInstructionsFilesAction); -}; +} export class ChatInstructionsPickerPick implements IChatContextPickerItem { @@ -241,14 +225,15 @@ export class ChatInstructionsPickerPick implements IChatContextPickerItem { constructor( @IPromptsService private readonly promptsService: IPromptsService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { } isEnabled(widget: IChatWidget): Promise | boolean { - return widget.attachmentModel.promptInstructions.featureEnabled; + return PromptsConfig.enabled(this.configurationService); } - asPicker(): { readonly placeholder: string; readonly picks: Promise } { + asPicker(): IChatContextPicker { const picks = this.promptsService.listPromptFiles(PromptsType.instructions, CancellationToken.None).then(value => { @@ -273,12 +258,7 @@ export class ChatInstructionsPickerPick implements IChatContextPickerItem { result.push({ label: getCleanPromptName(uri), asAttachment: (): IPromptFileVariableEntry => { - return { - kind: 'promptFile', - id: uri.toString(), - value: uri, - name: this.labelService.getUriBasenameLabel(uri), - }; + return toPromptFileVariableEntry(uri, true); } }); } @@ -287,8 +267,13 @@ export class ChatInstructionsPickerPick implements IChatContextPickerItem { return { placeholder: localize('placeholder', 'Select instructions files to attach'), - picks + picks, + configure: { + label: localize('configureInstructions', 'Configure Instructions...'), + commandId: CONFIGURE_INSTRUCTIONS_ACTION_ID + } }; } + } diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatModeActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts similarity index 57% rename from src/vs/workbench/contrib/chat/browser/actions/promptActions/chatModeActions.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts index a8df9776fc7..a63ca141f3b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatModeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts @@ -3,19 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CHAT_CATEGORY } from '../chatActions.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { ChatContextKeys } from '../../../common/chatContextKeys.js'; -import { localize, localize2 } from '../../../../../../nls.js'; -import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js'; -import { PromptFilePickers } from './dialogs/askToSelectPrompt/promptFilePickers.js'; -import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; -import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; -import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; -import { ChatViewId } from '../../chat.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; +import { PromptFilePickers } from './pickers/promptFilePickers.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { ChatViewId } from '../chat.js'; /** * Action ID for the `Configure Custom Chat Mode` action. @@ -26,8 +26,8 @@ class ManageModeAction extends Action2 { constructor() { super({ id: COMFIGURE_MODES_ACTION_ID, - title: localize2('configure-modes', "Configure Chat Modes"), - shortTitle: localize('manage-mode', "Configure Modes"), + title: localize2('configure-modes', "Configure Chat Modes..."), + shortTitle: localize('manage-mode', "Configure Modes..."), icon: Codicon.bookmark, f1: true, precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), @@ -46,9 +46,7 @@ class ManageModeAction extends Action2 { }); } - public override async run( - accessor: ServicesAccessor, - ): Promise { + public override async run(accessor: ServicesAccessor): Promise { const openerService = accessor.get(IOpenerService); const instaService = accessor.get(IInstantiationService); @@ -69,6 +67,6 @@ class ManageModeAction extends Action2 { /** * Helper to register all the `Run Current Prompt` actions. */ -export const registerChatModeActions = () => { +export function registerChatModeActions(): void { registerAction2(ManageModeAction); -}; +} diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/errors.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/errors.ts deleted file mode 100644 index 5d7ed5c8416..00000000000 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/errors.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from '../../../../../../../nls.js'; - -/** - * Base class for all prompt creation errors. - */ -class BaseCreatePromptError extends Error { } - -/** - * Error for when a folder already exists at the provided - * prompt file path. - */ -export class FolderExists extends BaseCreatePromptError { - constructor(path: string) { - super(localize( - 'workbench.command.prompts.create.error.folder-exists', - "Folder already exists at '{0}'.", - path, - )); - } -} - -/** - * Error for when an invalid prompt file name is provided. - */ -export class InvalidPromptName extends BaseCreatePromptError { - constructor(name: string) { - super(localize( - 'workbench.command.prompts.create.error.invalid-prompt-name', - "Invalid prompt file name '{0}'.", - name, - )); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/utils/createPromptFile.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/utils/createPromptFile.ts deleted file mode 100644 index 790ec76fad8..00000000000 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/utils/createPromptFile.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { FolderExists, InvalidPromptName } from '../errors.js'; -import { URI } from '../../../../../../../../base/common/uri.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { dirname } from '../../../../../../../../base/common/resources.js'; -import { IFileService } from '../../../../../../../../platform/files/common/files.js'; -import { isPromptOrInstructionsFile, PROMPT_FILE_EXTENSION } from '../../../../../../../../platform/prompts/common/prompts.js'; - -/** - * Options for the {@link createPromptFile} utility. - */ -interface ICreatePromptFileOptions { - /** - * Name of the prompt file including file extension. - * The file extension must be {@link PROMPT_FILE_EXTENSION}. - */ - readonly fileName: string; - - /** - * Destination folder of the prompt file. - */ - readonly folder: URI; - - /** - * Initial contents of the prompt file. - */ - readonly content: string; -} - -/** - * Create a prompt file at the provided folder and with - * the provided file content. - * - * @throws in the following cases: - * - if the `fileName` does not end with {@link PROMPT_FILE_EXTENSION} - * - if a folder or file with the same already name exists in the destination folder - */ -export async function createPromptFile( - fileService: IFileService, - options: ICreatePromptFileOptions, -): Promise { - const { fileName, folder, content } = options; - - const promptUri = URI.joinPath(folder, fileName); - - assert( - isPromptOrInstructionsFile(promptUri), - new InvalidPromptName(fileName), - ); - - // if a folder or file with the same name exists, throw an error - if (await fileService.exists(promptUri)) { - const promptInfo = await fileService.resolve(promptUri); - - // if existing object is a folder, throw an error - assert( - !promptInfo.isDirectory, - new FolderExists(promptUri.fsPath), - ); - - return promptUri; - } - - // ensure the parent folder of the prompt file exists - await fileService.createFolder(dirname(promptUri)); - - // create the prompt file with the provided text content - await fileService.createFile(promptUri, VSBuffer.fromString(content)); - - return promptUri; -} diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/createPromptCommand.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts similarity index 65% rename from src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/createPromptCommand.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts index 61919616c16..7f3a512661f 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/createPromptCommand.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts @@ -3,34 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isEqual } from '../../../../../../../base/common/resources.js'; -import { URI } from '../../../../../../../base/common/uri.js'; -import { getCodeEditor } from '../../../../../../../editor/browser/editorBrowser.js'; -import { SnippetController2 } from '../../../../../../../editor/contrib/snippet/browser/snippetController2.js'; -import { localize } from '../../../../../../../nls.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../../../platform/actions/common/actions.js'; -import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr } from '../../../../../../../platform/contextkey/common/contextkey.js'; -import { IFileService } from '../../../../../../../platform/files/common/files.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../../../../platform/instantiation/common/instantiation.js'; -import { KeybindingWeight } from '../../../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { ILogService } from '../../../../../../../platform/log/common/log.js'; -import { INotificationService, NeverShowAgainScope, Severity } from '../../../../../../../platform/notification/common/notification.js'; -import { IOpenerService } from '../../../../../../../platform/opener/common/opener.js'; -import { PromptsConfig } from '../../../../../../../platform/prompts/common/config.js'; -import { PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; -import { IUserDataSyncEnablementService, SyncResource } from '../../../../../../../platform/userDataSync/common/userDataSync.js'; -import { IEditorService } from '../../../../../../services/editor/common/editorService.js'; -import { CONFIGURE_SYNC_COMMAND_ID } from '../../../../../../services/userDataSync/common/userDataSync.js'; -import { ISnippetsService } from '../../../../../snippets/browser/snippets.js'; -import { ChatContextKeys } from '../../../../common/chatContextKeys.js'; -import { getLanguageIdForPromptsType } from '../../../../common/promptSyntax/constants.js'; -import { CHAT_CATEGORY } from '../../../actions/chatActions.js'; -import { askForPromptFileName } from './dialogs/askForPromptName.js'; -import { askForPromptSourceFolder } from './dialogs/askForPromptSourceFolder.js'; -import { createPromptFile } from './utils/createPromptFile.js'; +import { isEqual } from '../../../../../base/common/resources.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { SnippetController2 } from '../../../../../editor/contrib/snippet/browser/snippetController2.js'; +import { localize } from '../../../../../nls.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { INotificationService, NeverShowAgainScope, Severity } from '../../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; +import { getLanguageIdForPromptsType, PromptsType } from '../../common/promptSyntax/promptTypes.js'; +import { IUserDataSyncEnablementService, SyncResource } from '../../../../../platform/userDataSync/common/userDataSync.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { CONFIGURE_SYNC_COMMAND_ID } from '../../../../services/userDataSync/common/userDataSync.js'; +import { ISnippetsService } from '../../../snippets/browser/snippets.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { askForPromptFileName } from './pickers/askForPromptName.js'; +import { askForPromptSourceFolder } from './pickers/askForPromptSourceFolder.js'; -class AbstractNewPromptOrInstructionsFileAction extends Action2 { + +class AbstractNewPromptFileAction extends Action2 { constructor(id: string, title: string, private readonly type: PromptsType) { super({ @@ -70,11 +69,12 @@ class AbstractNewPromptOrInstructionsFileAction extends Action2 { return; } - const promptUri = await createPromptFile(fileService, { - fileName, - folder: selectedFolder.uri, - content: '' - }); + // create the prompt file + + await fileService.createFolder(selectedFolder.uri); + + const promptUri = URI.joinPath(selectedFolder.uri, fileName); + await fileService.createFile(promptUri); await openerService.open(promptUri); @@ -150,24 +150,26 @@ export const NEW_PROMPT_COMMAND_ID = 'workbench.command.new.prompt'; export const NEW_INSTRUCTIONS_COMMAND_ID = 'workbench.command.new.instructions'; export const NEW_MODE_COMMAND_ID = 'workbench.command.new.mode'; -class NewPromptFileAction extends AbstractNewPromptOrInstructionsFileAction { +class NewPromptFileAction extends AbstractNewPromptFileAction { constructor() { super(NEW_PROMPT_COMMAND_ID, localize('commands.new.prompt.local.title', "New Prompt File..."), PromptsType.prompt); } } -class NewInstructionsFileAction extends AbstractNewPromptOrInstructionsFileAction { +class NewInstructionsFileAction extends AbstractNewPromptFileAction { constructor() { super(NEW_INSTRUCTIONS_COMMAND_ID, localize('commands.new.instructions.local.title', "New Instructions File..."), PromptsType.instructions); } } -class NewModeFileAction extends AbstractNewPromptOrInstructionsFileAction { +class NewModeFileAction extends AbstractNewPromptFileAction { constructor() { super(NEW_MODE_COMMAND_ID, localize('commands.new.mode.local.title', "New Mode File..."), PromptsType.mode); } } -registerAction2(NewPromptFileAction); -registerAction2(NewInstructionsFileAction); -registerAction2(NewModeFileAction); +export function registerNewPromptFileActions(): void { + registerAction2(NewPromptFileAction); + registerAction2(NewInstructionsFileAction); + registerAction2(NewModeFileAction); +} diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptName.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts similarity index 81% rename from src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptName.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts index cdab1994fda..af69d44f8ee 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptName.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../../../../nls.js'; -import { getPromptFileExtension, PromptsType } from '../../../../../../../../platform/prompts/common/prompts.js'; -import { IQuickInputService } from '../../../../../../../../platform/quickinput/common/quickInput.js'; -import { URI } from '../../../../../../../../base/common/uri.js'; -import { IFileService } from '../../../../../../../../platform/files/common/files.js'; -import Severity from '../../../../../../../../base/common/severity.js'; -import { isValidBasename } from '../../../../../../../../base/common/extpath.js'; -import { ServicesAccessor } from '../../../../../../../../editor/browser/editorExtensions.js'; +import { localize } from '../../../../../../nls.js'; +import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; +import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { IQuickInputService } from '../../../../../../platform/quickinput/common/quickInput.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import Severity from '../../../../../../base/common/severity.js'; +import { isValidBasename } from '../../../../../../base/common/extpath.js'; +import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; /** * Asks the user for a file name. diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptSourceFolder.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts similarity index 86% rename from src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptSourceFolder.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts index 1f8c1f8601c..00c45a3d4c3 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptSourceFolder.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../../../../nls.js'; -import { URI } from '../../../../../../../../base/common/uri.js'; -import { WithUriValue } from '../../../../../../../../base/common/types.js'; -import { basename, extUri, isEqual } from '../../../../../../../../base/common/resources.js'; -import { ILabelService } from '../../../../../../../../platform/label/common/label.js'; -import { IOpenerService } from '../../../../../../../../platform/opener/common/opener.js'; -import { PROMPT_DOCUMENTATION_URL } from '../../../../../common/promptSyntax/constants.js'; -import { IWorkspaceContextService } from '../../../../../../../../platform/workspace/common/workspace.js'; -import { IPromptPath, IPromptsService } from '../../../../../common/promptSyntax/service/types.js'; -import { IPickOptions, IQuickInputService, IQuickPickItem } from '../../../../../../../../platform/quickinput/common/quickInput.js'; -import { ServicesAccessor } from '../../../../../../../../platform/instantiation/common/instantiation.js'; -import { PromptsType } from '../../../../../../../../platform/prompts/common/prompts.js'; +import { basename, extUri, isEqual } from '../../../../../../base/common/resources.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; +import { localize } from '../../../../../../nls.js'; +import { ILabelService } from '../../../../../../platform/label/common/label.js'; +import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; +import { PROMPT_DOCUMENTATION_URL, PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { IPickOptions, IQuickInputService, IQuickPickItem } from '../../../../../../platform/quickinput/common/quickInput.js'; +import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; +import { IPromptPath, IPromptsService } from '../../../common/promptSyntax/service/promptsService.js'; + interface IFolderQuickPickItem extends IQuickPickItem { readonly folder: IPromptPath; @@ -164,7 +163,7 @@ async function showNoFoldersDialog(accessor: ServicesAccessor, type: PromptsType const quickInputService = accessor.get(IQuickInputService); const openerService = accessor.get(IOpenerService); - const docsQuickPick: WithUriValue = { + const docsQuickPick: IQuickPickItem & { value: URI } = { type: 'item', label: getLearnLabel(type), description: PROMPT_DOCUMENTATION_URL, diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts similarity index 87% rename from src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index 95dd2fd5d36..32464f5b4b0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -3,29 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../../../../nls.js'; -import { URI } from '../../../../../../../../base/common/uri.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { Codicon } from '../../../../../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../../../../../base/common/themables.js'; -import { IPromptPath, IPromptsService } from '../../../../../common/promptSyntax/service/types.js'; -import { dirname, extUri, joinPath } from '../../../../../../../../base/common/resources.js'; -import { DisposableStore } from '../../../../../../../../base/common/lifecycle.js'; -import { IFileService } from '../../../../../../../../platform/files/common/files.js'; -import { ILabelService } from '../../../../../../../../platform/label/common/label.js'; -import { IOpenerService } from '../../../../../../../../platform/opener/common/opener.js'; -import { IDialogService } from '../../../../../../../../platform/dialogs/common/dialogs.js'; -import { ICommandService } from '../../../../../../../../platform/commands/common/commands.js'; -import { getCleanPromptName, PromptsType } from '../../../../../../../../platform/prompts/common/prompts.js'; -import { INSTRUCTIONS_DOCUMENTATION_URL, MODE_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../../../../../common/promptSyntax/constants.js'; -import { NEW_PROMPT_COMMAND_ID, NEW_INSTRUCTIONS_COMMAND_ID, NEW_MODE_COMMAND_ID } from '../../../../promptSyntax/contributions/createPromptCommand/createPromptCommand.js'; -import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent } from '../../../../../../../../platform/quickinput/common/quickInput.js'; -import { askForPromptFileName } from '../../../../promptSyntax/contributions/createPromptCommand/dialogs/askForPromptName.js'; -import { IInstantiationService } from '../../../../../../../../platform/instantiation/common/instantiation.js'; -import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; -import { askForPromptSourceFolder } from '../../../../promptSyntax/contributions/createPromptCommand/dialogs/askForPromptSourceFolder.js'; -import { UILabelProvider } from '../../../../../../../../base/common/keybindingLabels.js'; -import { OS } from '../../../../../../../../base/common/platform.js'; +import { localize } from '../../../../../../nls.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../../../base/common/themables.js'; +import { IPromptPath, IPromptsService } from '../../../common/promptSyntax/service/promptsService.js'; +import { dirname, extUri, joinPath } from '../../../../../../base/common/resources.js'; +import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { ILabelService } from '../../../../../../platform/label/common/label.js'; +import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; +import { IDialogService } from '../../../../../../platform/dialogs/common/dialogs.js'; +import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; +import { getCleanPromptName } from '../../../common/promptSyntax/config/promptFileLocations.js'; +import { PromptsType, INSTRUCTIONS_DOCUMENTATION_URL, MODE_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../../../common/promptSyntax/promptTypes.js'; +import { NEW_PROMPT_COMMAND_ID, NEW_INSTRUCTIONS_COMMAND_ID, NEW_MODE_COMMAND_ID } from '../newPromptFileActions.js'; +import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent } from '../../../../../../platform/quickinput/common/quickInput.js'; +import { askForPromptFileName } from './askForPromptName.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { UILabelProvider } from '../../../../../../base/common/keybindingLabels.js'; +import { OS } from '../../../../../../base/common/platform.js'; +import { askForPromptSourceFolder } from './askForPromptSourceFolder.js'; /** * Options for the {@link askToSelectInstructions} function. diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/index.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts similarity index 62% rename from src/vs/workbench/contrib/chat/browser/actions/promptActions/index.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts index a7f07d314d2..46b22c19fef 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/index.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts @@ -3,17 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerRunPromptActions } from './chatRunPromptAction.js'; -import { registerSaveToPromptActions } from './chatSaveToPromptAction.js'; -import { registerAttachPromptActions } from './chatAttachInstructionsAction.js'; +import { registerAttachPromptActions } from './attachInstructionsAction.js'; import { registerChatModeActions } from './chatModeActions.js'; +import { registerRunPromptActions } from './runPromptAction.js'; +import { registerSaveToPromptActions } from './saveToPromptAction.js'; +import { registerNewPromptFileActions } from './newPromptFileActions.js'; + /** * Helper to register all actions related to reusable prompt files. */ -export const registerPromptActions = () => { +export function registerPromptActions(): void { registerRunPromptActions(); registerAttachPromptActions(); registerSaveToPromptActions(); registerChatModeActions(); -}; + registerNewPromptFileActions(); +} diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 9a0e2cb461c..68edcd52a3a 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -15,9 +15,9 @@ import { CommandsRegistry } from '../../../../../platform/commands/common/comman import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { showToolsPicker } from '../actions/chatToolPicker.js'; import { ILanguageModelToolsService, IToolData, ToolSet } from '../../common/languageModelToolsService.js'; -import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../../common/promptSyntax/constants.js'; +import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../../common/promptSyntax/promptTypes.js'; import { PromptToolsMetadata } from '../../common/promptSyntax/parsers/promptHeader/metadata/tools.js'; -import { IPromptsService } from '../../common/promptSyntax/service/types.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { registerEditorFeature } from '../../../../../editor/common/editorFeatures.js'; class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider { @@ -49,7 +49,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider const parser = this.promptsService.getSyntaxParserFor(model); const { header } = await parser - .start() + .start(token) .settled(); if ((header === undefined) || token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatRunPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts similarity index 71% rename from src/vs/workbench/contrib/chat/browser/actions/promptActions/chatRunPromptAction.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts index b4e5b938af8..1198586f632 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatRunPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts @@ -3,34 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChatViewId, IChatWidget } from '../../chat.js'; -import { CHAT_CATEGORY } from '../chatActions.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { OS } from '../../../../../../base/common/platform.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { ChatContextKeys } from '../../../common/chatContextKeys.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import { ResourceContextKey } from '../../../../../common/contextkeys.js'; -import { KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js'; -import { PROMPT_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js'; -import { ILocalizedString, localize, localize2 } from '../../../../../../nls.js'; -import { UILabelProvider } from '../../../../../../base/common/keybindingLabels.js'; -import { ICommandAction } from '../../../../../../platform/action/common/action.js'; -import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js'; -import { IViewsService } from '../../../../../services/views/common/viewsService.js'; -import { PromptFilePickers } from './dialogs/askToSelectPrompt/promptFilePickers.js'; -import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; -import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; -import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { IRunPromptOptions, runPromptFile } from './dialogs/askToSelectPrompt/utils/runPrompt.js'; -import { ICodeEditorService } from '../../../../../../editor/browser/services/codeEditorService.js'; -import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; -import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; +import { ChatViewId, IChatWidget, showChatView } from '../chat.js'; +import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY } from '../actions/chatActions.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { OS } from '../../../../../base/common/platform.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { assertDefined } from '../../../../../base/common/types.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { ResourceContextKey } from '../../../../common/contextkeys.js'; +import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { PromptsType, PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js'; +import { ILocalizedString, localize, localize2 } from '../../../../../nls.js'; +import { UILabelProvider } from '../../../../../base/common/keybindingLabels.js'; +import { ICommandAction } from '../../../../../platform/action/common/action.js'; +import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { PromptFilePickers } from './pickers/promptFilePickers.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { getPromptCommandName } from '../../common/promptSyntax/service/promptsServiceImpl.js'; /** * Condition for the `Run Current Prompt` action. @@ -132,7 +131,7 @@ abstract class RunPromptBaseAction extends Action2 { resource: URI | undefined, inNewChat: boolean, accessor: ServicesAccessor, - ): Promise { + ): Promise { const viewsService = accessor.get(IViewsService); const commandService = accessor.get(ICommandService); @@ -142,15 +141,16 @@ abstract class RunPromptBaseAction extends Action2 { 'Cannot find URI resource for an active text editor.', ); - const { widget } = await runPromptFile( - resource, - { - inNewChat, - commandService, - viewsService, - }, - ); + if (inNewChat === true) { + await commandService.executeCommand(ACTION_ID_NEW_CHAT); + } + const widget = await showChatView(viewsService); + if (widget) { + widget.setInput(`/${getPromptCommandName(resource.path)}`); + // submit the prompt immediately + await widget.acceptInput(); + } return widget; } } @@ -177,7 +177,7 @@ class RunCurrentPromptAction extends RunPromptBaseAction { public override async run( accessor: ServicesAccessor, resource: URI | undefined, - ): Promise { + ): Promise { return await super.execute( resource, false, @@ -225,16 +225,18 @@ class RunSelectedPromptAction extends Action2 { } const { promptFile, keyMods } = result; - const runPromptOptions: IRunPromptOptions = { - inNewChat: keyMods.ctrlCmd, - viewsService, - commandService, - }; - const { widget } = await runPromptFile( - promptFile, - runPromptOptions, - ); - widget.focusInput(); + + if (keyMods.ctrlCmd === true) { + await commandService.executeCommand(ACTION_ID_NEW_CHAT); + } + + const widget = await showChatView(viewsService); + if (widget) { + widget.setInput(`/${getPromptCommandName(promptFile.path)}`); + // submit the prompt immediately + await widget.acceptInput(); + widget.focusInput(); + } } } @@ -242,7 +244,7 @@ class ManagePromptFilesAction extends Action2 { constructor() { super({ id: CONFIGURE_PROMPTS_ACTION_ID, - title: localize2('configure-prompts', "Configure Prompt Files"), + title: localize2('configure-prompts', "Configure Prompt Files..."), icon: Codicon.bookmark, f1: true, precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), @@ -281,16 +283,14 @@ class ManagePromptFilesAction extends Action2 { /** * Gets `URI` of a prompt file open in an active editor instance, if any. */ -export const getActivePromptFileUri = ( - accessor: ServicesAccessor, -): URI | undefined => { +function getActivePromptFileUri(accessor: ServicesAccessor): URI | undefined { const codeEditorService = accessor.get(ICodeEditorService); const model = codeEditorService.getActiveCodeEditor()?.getModel(); if (model?.getLanguageId() === PROMPT_LANGUAGE_ID) { return model.uri; } return undefined; -}; +} /** @@ -329,7 +329,7 @@ class RunCurrentPromptInNewChatAction extends RunPromptBaseAction { public override async run( accessor: ServicesAccessor, resource: URI, - ): Promise { + ): Promise { return await super.execute( resource, true, @@ -341,9 +341,9 @@ class RunCurrentPromptInNewChatAction extends RunPromptBaseAction { /** * Helper to register all the `Run Current Prompt` actions. */ -export const registerRunPromptActions = () => { +export function registerRunPromptActions(): void { registerAction2(RunCurrentPromptInNewChatAction); registerAction2(RunCurrentPromptAction); registerAction2(RunSelectedPromptAction); registerAction2(ManagePromptFilesAction); -}; +} diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatSaveToPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts similarity index 76% rename from src/vs/workbench/contrib/chat/browser/actions/promptActions/chatSaveToPromptAction.ts rename to src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts index 52a38b7ed80..d6d84c1f750 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatSaveToPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts @@ -3,22 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChatWidget } from '../../chat.js'; -import { CHAT_CATEGORY } from '../chatActions.js'; -import { localize2 } from '../../../../../../nls.js'; -import { IEditorPane } from '../../../../../common/editor.js'; -import { ChatContextKeys } from '../../../common/chatContextKeys.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { PROMPT_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js'; -import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js'; -import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; -import { IEditorService } from '../../../../../services/editor/common/editorService.js'; -import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; -import { ILanguageModelToolsService } from '../../../common/languageModelToolsService.js'; -import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { chatSubcommandLeader, IParsedChatRequest } from '../../../common/chatParserTypes.js'; -import { Action2, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; +import { assertDefined } from '../../../../../base/common/types.js'; +import { localize2 } from '../../../../../nls.js'; +import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; +import { IEditorPane } from '../../../../common/editor.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { chatSubcommandLeader, IParsedChatRequest } from '../../common/chatParserTypes.js'; +import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; +import { PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { IChatWidget } from '../chat.js'; + /** * Action ID for the `Save Prompt` action. @@ -138,9 +139,7 @@ class SaveToPromptAction extends Action2 { * Check if provided message belongs to the `save to prompt` slash * command itself that was run in the chat to invoke this action. */ -const isSaveToPromptSlashCommand = ( - message: IParsedChatRequest, -): boolean => { +function isSaveToPromptSlashCommand(message: IParsedChatRequest): boolean { const { parts } = message; if (parts.length < 1) { return false; @@ -156,14 +155,12 @@ const isSaveToPromptSlashCommand = ( } return true; -}; +} /** * Render the response part of a `request`/`response` turn pair. */ -const renderResponse = ( - response: string, -): string => { +function renderResponse(response: string): string { // if response starts with a code block, add an extra new line // before it, to prevent full blockquote from being be broken const delimiter = (response.startsWith('```')) @@ -175,25 +172,21 @@ const renderResponse = ( const quotedResponse = response.replaceAll('\n', '\n> '); return `> Copilot:${delimiter}${quotedResponse}`; -}; +} /** * Render a single `request`/`response` turn of the chat session. */ -const renderTurn = ( - turn: ITurn, -): string => { +function renderTurn(turn: ITurn): string { const { request, response } = turn; return `\n${request}\n\n${renderResponse(response)}`; -}; +} /** * Render the entire chat session as a markdown prompt. */ -const renderPrompt = ( - turns: readonly ITurn[], -): string => { +function renderPrompt(turns: readonly ITurn[]): string { const content: string[] = []; const allTools = new Set(); @@ -224,28 +217,24 @@ const renderPrompt = ( result.push(''); return result.join('\n'); -}; +} /** * Render the `tools` metadata inside prompt header. */ -const renderTools = ( - tools: Set, -): string => { +function renderTools(tools: Set): string { const toolStrings = [...tools].map((tool) => { return `'${tool}'`; }); return `tools: [${toolStrings.join(', ')}]`; -}; +} /** * Render prompt header. */ -const renderHeader = ( - tools: Set, -): string => { +function renderHeader(tools: Set): string { // skip rendering the header if no tools provided if (tools.size === 0) { return ''; @@ -256,7 +245,7 @@ const renderHeader = ( renderTools(tools), '---', ].join('\n'); -}; +} /** * Interface for a single `request`/`response` turn @@ -273,19 +262,19 @@ interface ITurn { * function instead of {@link SAVE_TO_PROMPT_ACTION_ID} directly to * encapsulate/enforce the correct options to be passed to the action. */ -export const runSaveToPromptAction = async ( +export function runSaveToPromptAction( options: ISaveToPromptActionOptions, commandService: ICommandService, -) => { - return await commandService.executeCommand( +) { + return commandService.executeCommand( SAVE_TO_PROMPT_ACTION_ID, options, ); -}; +} /** * Helper to register all the `Save Prompt` actions. */ -export const registerSaveToPromptActions = () => { +export function registerSaveToPromptActions(): void { registerAction2(SaveToPromptAction); -}; +} diff --git a/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts b/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts index 3756416c953..16b3b35113e 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts @@ -320,7 +320,7 @@ export class ConfigureToolSets extends Action2 { constructor() { super({ id: ConfigureToolSets.ID, - title: localize2('chat.configureToolSets', 'Configure Tool Sets'), + title: localize2('chat.configureToolSets', 'Configure Tool Sets...'), category: CHAT_CATEGORY, f1: true, precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.Tools.toolsCount.greater(0)), diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index dc121f603ea..267ec6bd626 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -138,7 +138,6 @@ export interface IChatAgentRequest { userSelectedModelId?: string; userSelectedTools?: Record; modeInstructions?: string; - toolSelectionIsExclusive?: boolean; editedFileEvents?: IChatAgentEditedFileEvent[]; } diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index b4159ed6c77..f6e2036e57f 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -57,3 +57,13 @@ export const chatEditedFileForeground = registerColor( export const chatRequestCodeBorder = registerColor('chat.requestCodeBorder', { dark: '#004972B8', light: '#0e639c40', hcDark: null, hcLight: null }, localize('chat.requestCodeBorder', 'Border color of code blocks within the chat request bubble.'), true); export const chatRequestBubbleBackground = registerColor('chat.requestBubbleBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hcDark: null, hcLight: null }, localize('chat.requestBubbleBackground', "Background color of the chat request bubble."), true); + +export const chatLinesAddedForeground = registerColor( + 'chat.linesAddedForeground', + { dark: '#54B054', light: '#107C10', hcDark: '#54B054', hcLight: '#107C10' }, + localize('chat.linesAddedForeground', 'Foreground color of lines added in chat code block pill.'), true); + +export const chatLinesRemovedForeground = registerColor( + 'chat.linesRemovedForeground', + { dark: '#FC6A6A', light: '#BC2F32', hcDark: '#F48771', hcLight: '#B5200D' }, + localize('chat.linesRemovedForeground', 'Foreground color of lines removed in chat code block pill.'), true); diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index aaf9bf48ac7..e28eaa47ecf 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -6,14 +6,16 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { IObservable, IReader, ITransaction } from '../../../../base/common/observable.js'; +import { IObservable, IReader } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; import { TextEdit } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; import { localize } from '../../../../nls.js'; import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditorPane } from '../../../common/editor.js'; import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; +import { IChatAgentResult } from './chatAgents.js'; import { ChatModel, IChatResponseModel } from './chatModel.js'; export const IChatEditingService = createDecorator('chatEditingService'); @@ -79,6 +81,24 @@ export interface IStreamingEdits { export const chatEditingSnapshotScheme = 'chat-editing-snapshot-text-model'; +export interface IModifiedEntryTelemetryInfo { + readonly agentId: string | undefined; + readonly command: string | undefined; + readonly sessionId: string; + readonly requestId: string; + readonly result: IChatAgentResult | undefined; +} + +export interface ISnapshotEntry { + readonly resource: URI; + readonly languageId: string; + readonly snapshotUri: URI; + readonly original: string; + readonly current: string; + readonly state: ModifiedFileEntryState; + telemetryInfo: IModifiedEntryTelemetryInfo; +} + export interface IChatEditingSession extends IDisposable { readonly isGlobalEditingSession: boolean; readonly chatSessionId: string; @@ -86,7 +106,6 @@ export interface IChatEditingSession extends IDisposable { readonly state: IObservable; readonly entries: IObservable; show(previousChanges?: boolean): Promise; - remove(...uris: URI[]): void; accept(...uris: URI[]): Promise; reject(...uris: URI[]): Promise; getEntry(uri: URI): IModifiedFileEntry | undefined; @@ -100,6 +119,10 @@ export interface IChatEditingSession extends IDisposable { */ getSnapshotUri(requestId: string, uri: URI, stopId: string | undefined): URI | undefined; + getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise; + + getSnapshot(requestId: string, undoStop: string | undefined, snapshotUri: URI): ISnapshotEntry | undefined; + /** * Will lead to this object getting disposed */ @@ -217,8 +240,8 @@ export interface IModifiedFileEntry { readonly waitsForLastEdits: IObservable; - accept(transaction: ITransaction | undefined): Promise; - reject(transaction: ITransaction | undefined): Promise; + accept(): Promise; + reject(): Promise; reviewMode: IObservable; autoAcceptController: IObservable<{ total: number; remaining: number; cancel(): void } | undefined>; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index d77b19bb594..4acb8302d77 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { asArray } from '../../../../base/common/arrays.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString, MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; @@ -18,265 +17,18 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI, UriComponents, UriDto, isUriComponents } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { IRange } from '../../../../editor/common/core/range.js'; -import { IOffsetRange, OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; -import { Location, SymbolKind, TextEdit, isLocation } from '../../../../editor/common/languages.js'; +import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; +import { TextEdit } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { IMarker, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; import { CellUri, ICellEditOperation } from '../../notebook/common/notebookCommon.js'; -import { ISCMHistoryItem } from '../../scm/common/history.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; -import { IChatRequestVariableValue } from './chatVariables.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; -interface IBaseChatRequestVariableEntry { - id: string; - fullName?: string; - icon?: ThemeIcon; - name: string; - modelDescription?: string; - - /** - * The offset-range in the prompt. This means this entry has been explicitly typed out - * by the user. - */ - range?: IOffsetRange; - value: IChatRequestVariableValue; - references?: IChatContentReference[]; - - omittedState?: OmittedState; -} - -export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry { - kind: 'generic'; -} - -export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry { - kind: 'directory'; -} - -export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry { - kind: 'file'; -} - -export const enum OmittedState { - NotOmitted, - Partial, - Full, -} - -export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'tool'; -} - -export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'toolset'; - readonly value: IChatRequestToolEntry[]; -} - -export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'implicit'; - readonly isFile: true; - readonly value: URI | Location | undefined; - readonly isSelection: boolean; - readonly isPromptFile: boolean; - enabled: boolean; -} - -export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'paste'; - code: string; - language: string; - pastedLines: string; - - // This is only used for old serialized data and should be removed once we no longer support it - fileName: string; - - // This is only undefined on old serialized data - copiedFrom: { - readonly uri: URI; - readonly range: IRange; - } | undefined; -} - -export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'symbol'; - readonly value: Location; - readonly symbolKind: SymbolKind; -} - -export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'command'; -} - -export interface IImageVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'image'; - readonly isPasted?: boolean; - readonly isURL?: boolean; - readonly mimeType?: string; -} - -export interface INotebookOutputVariableEntry extends Omit { - readonly kind: 'notebookOutput'; - readonly outputIndex?: number; - readonly mimeType?: string; -} - -export interface IDiagnosticVariableEntryFilterData { - readonly owner?: string; - readonly problemMessage?: string; - readonly filterUri?: URI; - readonly filterSeverity?: MarkerSeverity; - readonly filterRange?: IRange; -} - -/** - * Chat variable that represents an attached prompt file. - */ -export interface IPromptVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'file'; - readonly value: URI | Location; - readonly isRoot: boolean; - readonly modelDescription: string; -} - -export namespace IDiagnosticVariableEntryFilterData { - export const icon = Codicon.error; - - export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData { - return { - filterUri: marker.resource, - owner: marker.owner, - problemMessage: marker.message, - filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn } - }; - } - - export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry { - return { - id: id(data), - name: label(data), - icon, - value: data, - kind: 'diagnostic', - ...data, - }; - } - - export function id(data: IDiagnosticVariableEntryFilterData) { - return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber].join(':'); - } - - export function label(data: IDiagnosticVariableEntryFilterData) { - const enum TrimThreshold { - MaxChars = 30, - MaxSpaceLookback = 10, - } - if (data.problemMessage) { - if (data.problemMessage.length < TrimThreshold.MaxChars) { - return data.problemMessage; - } - - // Trim the message, on a space if it would not lose too much - // data (MaxSpaceLookback) or just blindly otherwise. - const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars); - if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) { - return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…'; - } - return data.problemMessage.substring(0, lastSpace) + '…'; - } - let labelStr = localize('chat.attachment.problems.all', "All Problems"); - if (data.filterUri) { - labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri)); - } - - return labelStr; - } -} - -export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData { - readonly kind: 'diagnostic'; -} - -export interface IElementVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'element'; -} - -export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'promptFile'; -} - -export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry { - readonly kind: 'scmHistoryItem'; - readonly value: URI; - readonly historyItem: ISCMHistoryItem; -} - -export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry - | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry - | IChatRequestToolEntry | IChatRequestToolSetEntry - | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry - | IPromptFileVariableEntry | ISCMHistoryItemVariableEntry; - - -export namespace IChatRequestVariableEntry { - - /** - * Returns URI of the passed variant entry. Return undefined if not found. - */ - export function toUri(entry: IChatRequestVariableEntry): URI | undefined { - return URI.isUri(entry.value) - ? entry.value - : isLocation(entry.value) - ? entry.value.uri - : undefined; - } -} - - -export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry { - return obj.kind === 'implicit'; -} - -export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry { - return obj.kind === 'paste'; -} - -export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry { - return obj.kind === 'image'; -} - -export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry { - return obj.kind === 'notebookOutput'; -} - -export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry { - return obj.kind === 'element'; -} - -export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry { - return obj.kind === 'diagnostic'; -} - -export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry { - return obj.kind === 'file'; -} - -export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry { - const entry = obj as IChatRequestVariableEntry; - return typeof entry === 'object' && - entry !== null && - typeof entry.id === 'string' && - typeof entry.name === 'string'; -} - -export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry { - return obj.kind === 'scmHistoryItem'; -} - export const CHAT_ATTACHABLE_IMAGE_MIME_TYPES: Record = { png: 'image/png', diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 5ac1a617205..b4a9d447569 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { localize } from '../../../../nls.js'; @@ -12,7 +13,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; import { ChatMode, modeToString } from './constants.js'; -import { ICustomChatMode, IPromptsService } from './promptSyntax/service/types.js'; +import { ICustomChatMode, IPromptsService } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); export interface IChatModeService { @@ -49,7 +50,7 @@ export class ChatModeService extends Disposable implements IChatModeService { private async refreshCustomPromptModes(fireChangeEvent?: boolean): Promise { try { - const modes = await this.promptsService.getCustomChatModes(); + const modes = await this.promptsService.getCustomChatModes(CancellationToken.None); this.latestCustomPromptModes = modes.map(customMode => new CustomChatMode(customMode)); this.hasCustomModes.set(modes.length > 0); if (fireChangeEvent) { diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index b13aa5c8a11..fefa954edf1 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -8,12 +8,12 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { IOffsetRange, OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData } from './chatModel.js'; import { IChatSlashData } from './chatSlashCommands.js'; import { IChatRequestProblemsVariable, IChatRequestVariableValue } from './chatVariables.js'; import { ChatAgentLocation } from './constants.js'; import { IToolData } from './languageModelToolsService.js'; -import { IChatPromptSlashCommand } from './promptSyntax/service/types.js'; +import { IChatPromptSlashCommand } from './promptSyntax/service/promptsService.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData } from './chatVariableEntries.js'; // These are in a separate file to avoid circular dependencies with the dependencies of the parser diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index dec8164ba76..37856c73242 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -12,7 +12,7 @@ import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatVariablesService, IDynamicVariable } from './chatVariables.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; import { IToolData, ToolSet } from './languageModelToolsService.js'; -import { IPromptsService } from './promptSyntax/service/types.js'; +import { IPromptsService } from './promptSyntax/service/promptsService.js'; const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2) diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index bdafb01973f..fd1c29a7a04 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -18,9 +18,10 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; import { IWorkspaceSymbol } from '../../search/common/search.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from './chatAgents.js'; -import { ChatModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatRequestVariableEntry, IChatResponseModel, IExportableChatData, ISerializableChatData } from './chatModel.js'; +import { ChatModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData } from './chatModel.js'; import { IParsedChatRequest } from './chatParserTypes.js'; import { IChatParserContext } from './chatRequestParser.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IChatRequestVariableValue } from './chatVariables.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; import { IPreparedToolInvocation, IToolConfirmationMessages, IToolResult } from './languageModelToolsService.js'; @@ -489,7 +490,6 @@ export interface IChatSendRequestOptions { userSelectedModelId?: string; userSelectedTools?: Record; modeInstructions?: string; - toolSelectionIsExclusive?: boolean; location?: ChatAgentLocation; locationData?: IChatLocationData; parserContext?: IChatParserContext; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 33d0893be93..cec924742db 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -27,7 +27,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from './chatAgents.js'; -import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatRequestVariableEntry, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, isImageVariableEntry, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; +import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, getPromptText } from './chatParserTypes.js'; import { ChatRequestParser } from './chatRequestParser.js'; import { IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js'; @@ -35,6 +35,7 @@ import { ChatServiceTelemetry } from './chatServiceTelemetry.js'; import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatTransferService } from './chatTransferService.js'; +import { IChatRequestVariableEntry, isImageVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatMode } from './constants.js'; import { ChatMessageRole, IChatMessage, ILanguageModelsService } from './languageModels.js'; import { ILanguageModelToolsService } from './languageModelToolsService.js'; @@ -772,7 +773,6 @@ export class ChatService extends Disposable implements IChatService { userSelectedModelId: options?.userSelectedModelId, userSelectedTools: options?.userSelectedTools, modeInstructions: options?.modeInstructions, - toolSelectionIsExclusive: options?.toolSelectionIsExclusive, editedFileEvents: request.editedFileEvents } satisfies IChatAgentRequest; }; diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts new file mode 100644 index 00000000000..530a3530850 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -0,0 +1,309 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from '../../../../base/common/codicons.js'; +import { basename } from '../../../../base/common/resources.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; +import { isLocation, Location, SymbolKind } from '../../../../editor/common/languages.js'; +import { localize } from '../../../../nls.js'; +import { MarkerSeverity, IMarker } from '../../../../platform/markers/common/markers.js'; +import { ISCMHistoryItem } from '../../scm/common/history.js'; +import { IChatContentReference } from './chatService.js'; +import { IChatRequestVariableValue } from './chatVariables.js'; + + +interface IBaseChatRequestVariableEntry { + readonly id: string; + readonly fullName?: string; + readonly icon?: ThemeIcon; + readonly name: string; + readonly modelDescription?: string; + + /** + * The offset-range in the prompt. This means this entry has been explicitly typed out + * by the user. + */ + readonly range?: IOffsetRange; + readonly value: IChatRequestVariableValue; + readonly references?: IChatContentReference[]; + + omittedState?: OmittedState; +} + +export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry { + kind: 'generic'; +} + +export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry { + kind: 'directory'; +} + +export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry { + kind: 'file'; +} + +export const enum OmittedState { + NotOmitted, + Partial, + Full, +} + +export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'tool'; +} + +export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'toolset'; + readonly value: IChatRequestToolEntry[]; +} + +export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'implicit'; + readonly isFile: true; + readonly value: URI | Location | undefined; + readonly isSelection: boolean; + readonly enabled: boolean; +} + +export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'paste'; + readonly code: string; + readonly language: string; + readonly pastedLines: string; + + // This is only used for old serialized data and should be removed once we no longer support it + readonly fileName: string; + + // This is only undefined on old serialized data + readonly copiedFrom: { + readonly uri: URI; + readonly range: IRange; + } | undefined; +} + +export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'symbol'; + readonly value: Location; + readonly symbolKind: SymbolKind; +} + +export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'command'; +} + +export interface IImageVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'image'; + readonly isPasted?: boolean; + readonly isURL?: boolean; + readonly mimeType?: string; +} + +export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'notebookOutput'; + readonly outputIndex?: number; + readonly mimeType?: string; +} + +export interface IDiagnosticVariableEntryFilterData { + readonly owner?: string; + readonly problemMessage?: string; + readonly filterUri?: URI; + readonly filterSeverity?: MarkerSeverity; + readonly filterRange?: IRange; +} + + + +export namespace IDiagnosticVariableEntryFilterData { + export const icon = Codicon.error; + + export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData { + return { + filterUri: marker.resource, + owner: marker.owner, + problemMessage: marker.message, + filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn } + }; + } + + export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry { + return { + id: id(data), + name: label(data), + icon, + value: data, + kind: 'diagnostic', + ...data, + }; + } + + export function id(data: IDiagnosticVariableEntryFilterData) { + return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber].join(':'); + } + + export function label(data: IDiagnosticVariableEntryFilterData) { + const enum TrimThreshold { + MaxChars = 30, + MaxSpaceLookback = 10, + } + if (data.problemMessage) { + if (data.problemMessage.length < TrimThreshold.MaxChars) { + return data.problemMessage; + } + + // Trim the message, on a space if it would not lose too much + // data (MaxSpaceLookback) or just blindly otherwise. + const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars); + if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) { + return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…'; + } + return data.problemMessage.substring(0, lastSpace) + '…'; + } + let labelStr = localize('chat.attachment.problems.all', "All Problems"); + if (data.filterUri) { + labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri)); + } + + return labelStr; + } +} + +export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData { + readonly kind: 'diagnostic'; +} + +export interface IElementVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'element'; +} + +export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'promptFile'; + readonly value: URI; + readonly isRoot: boolean; + readonly modelDescription: string; +} + +export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'scmHistoryItem'; + readonly value: URI; + readonly historyItem: ISCMHistoryItem; +} + +export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry + | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry + | IChatRequestToolEntry | IChatRequestToolSetEntry + | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry + | IPromptFileVariableEntry | ISCMHistoryItemVariableEntry; + + +export namespace IChatRequestVariableEntry { + + /** + * Returns URI of the passed variant entry. Return undefined if not found. + */ + export function toUri(entry: IChatRequestVariableEntry): URI | undefined { + return URI.isUri(entry.value) + ? entry.value + : isLocation(entry.value) + ? entry.value.uri + : undefined; + } +} + + +export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry { + return obj.kind === 'implicit'; +} + +export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry { + return obj.kind === 'paste'; +} + +export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry { + return obj.kind === 'image'; +} + +export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry { + return obj.kind === 'notebookOutput'; +} + +export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry { + return obj.kind === 'element'; +} + +export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry { + return obj.kind === 'diagnostic'; +} + +export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry { + return obj.kind === 'file'; +} + +export function isPromptFileVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptFileVariableEntry { + return obj.kind === 'promptFile'; +} + +export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry { + const entry = obj as IChatRequestVariableEntry; + return typeof entry === 'object' && + entry !== null && + typeof entry.id === 'string' && + typeof entry.name === 'string'; +} + +export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry { + return obj.kind === 'scmHistoryItem'; +} + +/** + * Utility to convert a {@link uri} to a chat variable entry. + * The `id` of the chat variable can be one of the following: + * + * - `vscode.prompt.instructions__`: for all non-root prompt file references + * - `vscode.prompt.instructions.root__`: for *root* prompt file references + * - ``: for the rest of references(the ones that do not point to a prompt file) + * + * @param uri A resource URI that points to a prompt instructions file. + * @param isRoot If the reference is the root reference in the references tree. + * This object most likely was explicitly attached by the user. + */ +export function toPromptFileVariableEntry(uri: URI, isRoot: boolean): IPromptFileVariableEntry { + return { + // `id` for all `prompt files` starts with the well-defined part that the copilot extension(or other chatbot) can rely on + id: `vscode.prompt.instructions${isRoot ? '.root' : ''}}__${uri.toString()}`, + name: `prompt:${basename(uri)}`, + value: uri, + kind: 'promptFile', + modelDescription: 'Prompt instructions file', + isRoot, + }; +} + +export class ChatRequestVariableSet { + private _entries = new Map(); + + public add(...entry: IChatRequestVariableEntry[]): void { + for (const e of entry) { + if (!this._entries.has(e.id)) { + this._entries.set(e.id, e); + } + } + } + + public remove(entry: IChatRequestVariableEntry): void { + this._entries.delete(entry.id); + } + + public has(entry: IChatRequestVariableEntry): boolean { + return this._entries.has(entry.id); + } + + public asArray(): IChatRequestVariableEntry[] { + return Array.from(this._entries.values()); + } +} diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index 93bbd3f343d..d1b1a554ea8 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -9,8 +9,9 @@ import { URI } from '../../../../base/common/uri.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { Location } from '../../../../editor/common/languages.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IChatModel, IDiagnosticVariableEntryFilterData } from './chatModel.js'; +import { IChatModel } from './chatModel.js'; import { IChatContentReference, IChatProgressMessage } from './chatService.js'; +import { IDiagnosticVariableEntryFilterData } from './chatVariableEntries.js'; import { IToolData, ToolSet } from './languageModelToolsService.js'; export interface IChatVariableData { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index a23e12b24cb..3ab1a20efe0 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -15,7 +15,8 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ILogService } from '../../../../platform/log/common/log.js'; import { annotateVulnerabilitiesInText } from './annotations.js'; import { getFullyQualifiedId, IChatAgentCommand, IChatAgentData, IChatAgentNameService, IChatAgentResult } from './chatAgents.js'; -import { ChatPauseState, IChatModel, IChatProgressRenderableResponseContent, IChatRequestDisablement, IChatRequestModel, IChatRequestVariableEntry, IChatResponseModel, IChatTextEditGroup, IResponse } from './chatModel.js'; +import { ChatPauseState, IChatModel, IChatProgressRenderableResponseContent, IChatRequestDisablement, IChatRequestModel, IChatResponseModel, IChatTextEditGroup, IResponse } from './chatModel.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatCodeCitation, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseErrorDetails, IChatTask, IChatUsedContext } from './chatService.js'; import { countWords } from './chatWordCounter.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts index 9386d7f3f46..ac2590f8388 100644 --- a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts +++ b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts @@ -9,7 +9,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { Memento } from '../../../common/memento.js'; import { ModifiedFileEntryState } from './chatEditingService.js'; -import { IChatRequestVariableEntry } from './chatModel.js'; +import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IChatMode } from './chatModes.js'; import { CHAT_PROVIDER_ID } from './chatParticipantContribTypes.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; diff --git a/src/vs/base/common/codecs/asyncDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts similarity index 97% rename from src/vs/base/common/codecs/asyncDecoder.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts index baf3c6eacf7..be10b50ebba 100644 --- a/src/vs/base/common/codecs/asyncDecoder.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from '../lifecycle.js'; +import { Disposable } from '../../../../../../../base/common/lifecycle.js'; import { BaseDecoder } from './baseDecoder.js'; /** diff --git a/src/vs/base/common/codecs/baseDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts similarity index 95% rename from src/vs/base/common/codecs/baseDecoder.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts index 89e16680383..2d0780e7d7c 100644 --- a/src/vs/base/common/codecs/baseDecoder.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from '../event.js'; -import { ReadableStream } from '../stream.js'; -import { DeferredPromise } from '../async.js'; +import { Emitter } from '../../../../../../../base/common/event.js'; +import { ReadableStream } from '../../../../../../../base/common/stream.js'; +import { DeferredPromise } from '../../../../../../../base/common/async.js'; import { AsyncDecoder } from './asyncDecoder.js'; -import { assert, assertNever } from '../assert.js'; -import { DisposableMap, IDisposable } from '../lifecycle.js'; -import { ObservableDisposable } from '../observableDisposable.js'; +import { assert, assertNever } from '../../../../../../../base/common/assert.js'; +import { DisposableMap, IDisposable } from '../../../../../../../base/common/lifecycle.js'; +import { ObservableDisposable } from '../../utils/observableDisposable.js'; /** * Event names of {@link ReadableStream} stream. diff --git a/src/vs/editor/common/codecs/baseToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts similarity index 94% rename from src/vs/editor/common/codecs/baseToken.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts index 72f1d491458..ba81a78738e 100644 --- a/src/vs/editor/common/codecs/baseToken.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { pick } from '../../../base/common/arrays.js'; -import { assert } from '../../../base/common/assert.js'; -import { IRange, Range } from '../../../editor/common/core/range.js'; +import { assert } from '../../../../../../../base/common/assert.js'; +import { IRange, Range } from '../../../../../../../editor/common/core/range.js'; /** * Base class for all tokens with a `range` that reflects @@ -90,7 +89,7 @@ export abstract class BaseToken { tokens: readonly BaseToken[], delimiter: string = '', ): string { - return tokens.map(pick('text')).join(delimiter); + return tokens.map(token => token.text).join(delimiter); } /** diff --git a/src/vs/editor/common/codecs/compositeToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/compositeToken.ts similarity index 100% rename from src/vs/editor/common/codecs/compositeToken.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/compositeToken.ts diff --git a/src/vs/editor/common/codecs/frontMatterCodec/constants.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts similarity index 90% rename from src/vs/editor/common/codecs/frontMatterCodec/constants.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts index c72dddf085c..779e53a1ef1 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/constants.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts @@ -5,7 +5,7 @@ import { NewLine } from '../linesCodec/tokens/newLine.js'; import { CarriageReturn } from '../linesCodec/tokens/carriageReturn.js'; -import { FormFeed, SpacingToken } from '../simpleCodec/tokens/index.js'; +import { FormFeed, SpacingToken } from '../simpleCodec/tokens/tokens.js'; /** * List of valid "space" tokens that are valid between different diff --git a/src/vs/editor/common/codecs/frontMatterCodec/frontMatterDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts similarity index 80% rename from src/vs/editor/common/codecs/frontMatterCodec/frontMatterDecoder.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts index 3eb7369f17b..704e10f1754 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/frontMatterDecoder.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts @@ -3,16 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Word } from '../simpleCodec/tokens/index.js'; -import { ObjectStream } from '../utils/objectStream.js'; -import { assert } from '../../../../base/common/assert.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; +import { Word } from '../simpleCodec/tokens/tokens.js'; +import { assert } from '../../../../../../../../base/common/assert.js'; +import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; import { VALID_INTER_RECORD_SPACING_TOKENS } from './constants.js'; -import { ReadableStream } from '../../../../base/common/stream.js'; +import { ReadableStream } from '../../../../../../../../base/common/stream.js'; import { FrontMatterToken, FrontMatterRecord } from './tokens/index.js'; -import { BaseDecoder } from '../../../../base/common/codecs/baseDecoder.js'; +import { BaseDecoder } from '../baseDecoder.js'; import { SimpleDecoder, type TSimpleDecoderToken } from '../simpleCodec/simpleDecoder.js'; -import { PartialFrontMatterRecord, PartialFrontMatterRecordName, PartialFrontMatterRecordNameWithDelimiter } from './parsers/frontMatterRecord/index.js'; +import { ObjectStream } from '../utils/objectStream.js'; +import { PartialFrontMatterRecordNameWithDelimiter } from './parsers/frontMatterRecord/frontMatterRecordNameWithDelimiter.js'; +import { PartialFrontMatterRecord } from './parsers/frontMatterRecord/frontMatterRecord.js'; +import { PartialFrontMatterRecordName } from './parsers/frontMatterRecord/frontMatterRecordName.js'; +import { FrontMatterParserFactory } from './parsers/frontMatterParserFactory.js'; /** * Tokens produced by this decoder. @@ -29,16 +32,17 @@ export class FrontMatterDecoder extends BaseDecoder | ObjectStream, ) { if (stream instanceof ObjectStream) { super(stream); - - return; + } else { + super(new SimpleDecoder(stream)); } - - super(new SimpleDecoder(stream)); + this.parserFactory = new FrontMatterParserFactory(); } protected override onStreamData(token: TSimpleDecoderToken): void { @@ -92,7 +96,7 @@ export class FrontMatterDecoder extends BaseDecoder { // comma or a closing square bracket must stop the parsing // process of the value represented by a generic sequence of tokens diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts new file mode 100644 index 00000000000..0be65371449 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { LeftBracket } from '../../simpleCodec/tokens/brackets.js'; +import { Word } from '../../simpleCodec/tokens/word.js'; +import { FrontMatterRecordDelimiter, FrontMatterRecordName } from '../tokens/frontMatterRecord.js'; +import { TQuoteToken } from '../tokens/frontMatterString.js'; +import { PartialFrontMatterArray } from './frontMatterArray.js'; +import { PartialFrontMatterRecord } from './frontMatterRecord/frontMatterRecord.js'; +import { PartialFrontMatterRecordName } from './frontMatterRecord/frontMatterRecordName.js'; +import { PartialFrontMatterRecordNameWithDelimiter, TNameStopToken } from './frontMatterRecord/frontMatterRecordNameWithDelimiter.js'; +import { PartialFrontMatterSequence } from './frontMatterSequence.js'; +import { PartialFrontMatterString } from './frontMatterString.js'; +import { PartialFrontMatterValue } from './frontMatterValue.js'; + +export class FrontMatterParserFactory { + createRecord(tokens: [FrontMatterRecordName, FrontMatterRecordDelimiter]): PartialFrontMatterRecord { + return new PartialFrontMatterRecord(this, tokens); + } + createRecordName(startToken: Word): PartialFrontMatterRecordName { + return new PartialFrontMatterRecordName(this, startToken); + } + createRecordNameWithDelimiter(tokens: readonly [FrontMatterRecordName, TNameStopToken]): PartialFrontMatterRecordNameWithDelimiter { + return new PartialFrontMatterRecordNameWithDelimiter(this, tokens); + } + createArray(startToken: LeftBracket) { + return new PartialFrontMatterArray(this, startToken); + } + createValue(shouldStop: (token: BaseToken) => boolean): PartialFrontMatterValue { + return new PartialFrontMatterValue(this, shouldStop); + } + createString(startToken: TQuoteToken): PartialFrontMatterString { + return new PartialFrontMatterString(startToken); + } + createSequence(shouldStop: (token: BaseToken) => boolean): PartialFrontMatterSequence { + return new PartialFrontMatterSequence(shouldStop); + } +} diff --git a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts similarity index 91% rename from src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts index 64d98b56c95..e4afee055b5 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts @@ -6,14 +6,15 @@ import { BaseToken } from '../../../baseToken.js'; import { NewLine } from '../../../linesCodec/tokens/newLine.js'; import { PartialFrontMatterValue } from '../frontMatterValue.js'; -import { assertNever } from '../../../../../../base/common/assert.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; +import { assertNever } from '../../../../../../../../../../base/common/assert.js'; +import { assertDefined } from '../../../../../../../../../../base/common/types.js'; import { PartialFrontMatterSequence } from '../frontMatterSequence.js'; import { CarriageReturn } from '../../../linesCodec/tokens/carriageReturn.js'; import { type TSimpleDecoderToken } from '../../../simpleCodec/simpleDecoder.js'; -import { Word, FormFeed, SpacingToken } from '../../../simpleCodec/tokens/index.js'; +import { Word, FormFeed, SpacingToken } from '../../../simpleCodec/tokens/tokens.js'; import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../../simpleCodec/parserBase.js'; import { FrontMatterValueToken, FrontMatterRecordName, FrontMatterRecordDelimiter, FrontMatterRecord } from '../../tokens/index.js'; +import { type FrontMatterParserFactory } from '../frontMatterParserFactory.js'; /** * Type of a next parser that can be returned by {@link PartialFrontMatterRecord}. @@ -44,6 +45,7 @@ export class PartialFrontMatterRecord extends ParserBase { +function shouldEndTokenSequence(token: BaseToken): token is (NewLine | CarriageReturn | FormFeed) { return ( (token instanceof NewLine) || (token instanceof CarriageReturn) || (token instanceof FormFeed) ); -}; +} diff --git a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts similarity index 86% rename from src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts index f1cc5afc9e8..84c94b75211 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts @@ -5,9 +5,10 @@ import { type TSimpleDecoderToken } from '../../../simpleCodec/simpleDecoder.js'; import { FrontMatterRecordName, type TRecordNameToken } from '../../tokens/index.js'; -import { Colon, Word, Dash, SpacingToken } from '../../../simpleCodec/tokens/index.js'; -import { PartialFrontMatterRecordNameWithDelimiter } from './frontMatterRecordNameWithDelimiter.js'; +import { Colon, Word, Dash, SpacingToken } from '../../../simpleCodec/tokens/tokens.js'; +import { type PartialFrontMatterRecordNameWithDelimiter } from './frontMatterRecordNameWithDelimiter.js'; import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../../simpleCodec/parserBase.js'; +import { type FrontMatterParserFactory } from '../frontMatterParserFactory.js'; /** * Tokens that can be used inside a record name. @@ -30,6 +31,7 @@ type TNextParser = PartialFrontMatterRecordName | PartialFrontMatterRecordNameWi */ export class PartialFrontMatterRecordName extends ParserBase { constructor( + private readonly factory: FrontMatterParserFactory, startToken: Word, ) { super([startToken]); @@ -57,7 +59,7 @@ export class PartialFrontMatterRecordName extends ParserBase { constructor( + private readonly factory: FrontMatterParserFactory, tokens: readonly [FrontMatterRecordName, TNameStopToken], ) { super([...tokens]); @@ -63,7 +65,7 @@ export class PartialFrontMatterRecordNameWithDelimiter extends ParserBase< this.isConsumed = true; return { result: 'success', - nextParser: new PartialFrontMatterRecord( + nextParser: this.factory.createRecord( [recordName, recordDelimiter], ), wasTokenConsumed: true, diff --git a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterSequence.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterSequence.ts similarity index 100% rename from src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterSequence.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterSequence.ts diff --git a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterString.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts similarity index 90% rename from src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterString.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts index 00f8b43301b..d0ed92746d6 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterString.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assert } from '../../../../../base/common/assert.js'; -import { SimpleToken } from '../../simpleCodec/tokens/index.js'; -import { assertDefined } from '../../../../../base/common/types.js'; +import { assert } from '../../../../../../../../../base/common/assert.js'; +import { SimpleToken } from '../../simpleCodec/tokens/tokens.js'; +import { assertDefined } from '../../../../../../../../../base/common/types.js'; import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; import { FrontMatterString, type TQuoteToken } from '../tokens/frontMatterString.js'; import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; diff --git a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterValue.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts similarity index 91% rename from src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterValue.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts index dc39f0ede56..710767aed40 100644 --- a/src/vs/editor/common/codecs/frontMatterCodec/parsers/frontMatterValue.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { BaseToken } from '../../baseToken.js'; -import { PartialFrontMatterArray } from './frontMatterArray.js'; -import { PartialFrontMatterString } from './frontMatterString.js'; +import { type PartialFrontMatterArray } from './frontMatterArray.js'; +import { type PartialFrontMatterString } from './frontMatterString.js'; import { asBoolean, FrontMatterBoolean } from '../tokens/frontMatterBoolean.js'; import { FrontMatterValueToken } from '../tokens/frontMatterToken.js'; import { PartialFrontMatterSequence } from './frontMatterSequence.js'; import { FrontMatterSequence } from '../tokens/frontMatterSequence.js'; import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { Word, Quote, DoubleQuote, LeftBracket } from '../../simpleCodec/tokens/index.js'; +import { Word, Quote, DoubleQuote, LeftBracket } from '../../simpleCodec/tokens/tokens.js'; import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; +import { type FrontMatterParserFactory } from './frontMatterParserFactory.js'; /** * List of tokens that can start a "value" sequence. @@ -54,6 +55,7 @@ export class PartialFrontMatterValue extends ParserBase[] = Obj * the {@link LinesDecoder} which emits {@link Line} tokens without them. */ const WORD_STOP_CHARACTERS: readonly string[] = Object.freeze( - WELL_KNOWN_TOKENS.map(pick('symbol')), + WELL_KNOWN_TOKENS.map(token => token.symbol), ); /** diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/angleBrackets.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/angleBrackets.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/at.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/at.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/at.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/at.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/brackets.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/brackets.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/colon.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/colon.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/colon.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/colon.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/comma.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/comma.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/comma.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/comma.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/curlyBraces.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/curlyBraces.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/curlyBraces.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/curlyBraces.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/dash.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dash.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/dash.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dash.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/dollarSign.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dollarSign.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/dollarSign.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dollarSign.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/doubleQuote.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/doubleQuote.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/exclamationMark.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/exclamationMark.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/formFeed.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/formFeed.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/hash.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/hash.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/hash.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/hash.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/parentheses.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/parentheses.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/quote.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/quote.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/quote.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/quote.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/simpleToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts similarity index 95% rename from src/vs/editor/common/codecs/simpleCodec/tokens/simpleToken.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts index dddd8b5a96f..5a611bb1ddd 100644 --- a/src/vs/editor/common/codecs/simpleCodec/tokens/simpleToken.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Range } from '../../../core/range.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; import { BaseToken } from '../../baseToken.js'; import { Line } from '../../linesCodec/tokens/line.js'; diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/slash.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/slash.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/slash.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/slash.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/space.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/space.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/space.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/space.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/tab.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tab.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/tab.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tab.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/index.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/index.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/verticalTab.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.ts similarity index 100% rename from src/vs/editor/common/codecs/simpleCodec/tokens/verticalTab.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.ts diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/word.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts similarity index 94% rename from src/vs/editor/common/codecs/simpleCodec/tokens/word.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts index 1629b38db47..bea656895fc 100644 --- a/src/vs/editor/common/codecs/simpleCodec/tokens/word.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts @@ -5,7 +5,7 @@ import { BaseToken } from '../../baseToken.js'; import { Line } from '../../linesCodec/tokens/line.js'; -import { Range } from '../../../../../editor/common/core/range.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; /** * A token that represent a word - a set of continuous diff --git a/src/vs/editor/common/codecs/textToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/textToken.ts similarity index 100% rename from src/vs/editor/common/codecs/textToken.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/textToken.ts diff --git a/src/vs/editor/common/codecs/utils/objectStream.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts similarity index 91% rename from src/vs/editor/common/codecs/utils/objectStream.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts index 3d88b36a167..3c8476c2d56 100644 --- a/src/vs/editor/common/codecs/utils/objectStream.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assertNever } from '../../../../base/common/assert.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { ObservableDisposable } from '../../../../base/common/observableDisposable.js'; -import { newWriteableStream, WriteableStream, ReadableStream } from '../../../../base/common/stream.js'; +import { assertNever } from '../../../../../../../../base/common/assert.js'; +import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; +import { ObservableDisposable } from '../../../utils/observableDisposable.js'; +import { newWriteableStream, ReadableStream, WriteableStream } from '../../../../../../../../base/common/stream.js'; + /** * A readable stream of provided objects. @@ -214,10 +215,10 @@ export class ObjectStream extends ObservableDisposable impleme /** * Create a generator out of a provided array. */ -export const arrayToGenerator = >(array: T[]): Generator => { +export function arrayToGenerator>(array: T[]): Generator { return (function* (): Generator { for (const item of array) { yield item; } })(); -}; +} diff --git a/src/vs/editor/common/codecs/utils/objectStreamFromTextModel.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts similarity index 78% rename from src/vs/editor/common/codecs/utils/objectStreamFromTextModel.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts index e92bc936aea..2a9b2173b5d 100644 --- a/src/vs/editor/common/codecs/utils/objectStreamFromTextModel.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITextModel } from '../../model.js'; +import { ITextModel } from '../../../../../../../../editor/common/model.js'; import { ObjectStream } from './objectStream.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; +import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; /** * Create new instance of the stream from a provided text model. @@ -21,9 +21,7 @@ export function objectStreamFromTextModel( /** * Create a generator out of a provided text model. */ -const modelToGenerator = ( - model: ITextModel, -): Generator => { +function modelToGenerator(model: ITextModel): Generator { return (function* (): Generator { const totalLines = model.getLineCount(); let currentLine = 1; @@ -45,4 +43,4 @@ const modelToGenerator = ( currentLine++; } })(); -}; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts index 85f62beb086..5ac4a138fbe 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts @@ -12,14 +12,14 @@ import { PartialPromptAtMention } from './parsers/promptAtMentionParser.js'; import { PromptTemplateVariable } from './tokens/promptTemplateVariable.js'; import { assert, assertNever } from '../../../../../../base/common/assert.js'; import { PartialPromptSlashCommand } from './parsers/promptSlashCommandParser.js'; -import { BaseDecoder } from '../../../../../../base/common/codecs/baseDecoder.js'; +import { BaseDecoder } from './base/baseDecoder.js'; import { PromptVariable, PromptVariableWithData } from './tokens/promptVariable.js'; -import { At } from '../../../../../../editor/common/codecs/simpleCodec/tokens/at.js'; -import { Hash } from '../../../../../../editor/common/codecs/simpleCodec/tokens/hash.js'; -import { Slash } from '../../../../../../editor/common/codecs/simpleCodec/tokens/slash.js'; -import { DollarSign } from '../../../../../../editor/common/codecs/simpleCodec/tokens/dollarSign.js'; +import { At } from './base/simpleCodec/tokens/at.js'; +import { Hash } from './base/simpleCodec/tokens/hash.js'; +import { Slash } from './base/simpleCodec/tokens/slash.js'; +import { DollarSign } from './base/simpleCodec/tokens/dollarSign.js'; import { PartialPromptVariableName, PartialPromptVariableWithData } from './parsers/promptVariableParser.js'; -import { MarkdownDecoder, TMarkdownToken } from '../../../../../../editor/common/codecs/markdownCodec/markdownDecoder.js'; +import { MarkdownDecoder, TMarkdownToken } from './base/markdownCodec/markdownDecoder.js'; import { PartialPromptTemplateVariable, PartialPromptTemplateVariableStart, TPromptTemplateVariableParser } from './parsers/promptTemplateVariableParser.js'; /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts index dad221df7f6..2173ba438ad 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts @@ -6,21 +6,21 @@ import { PromptAtMention } from '../tokens/promptAtMention.js'; import { assert } from '../../../../../../../base/common/assert.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; -import { At } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/at.js'; -import { Tab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/tab.js'; -import { Hash } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/hash.js'; -import { Space } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/space.js'; -import { Colon } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/colon.js'; -import { NewLine } from '../../../../../../../editor/common/codecs/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../../../../../../../editor/common/codecs/simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../../../../../../../editor/common/codecs/linesCodec/tokens/carriageReturn.js'; -import { ExclamationMark } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/exclamationMark.js'; -import { LeftBracket, RightBracket } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/brackets.js'; -import { LeftAngleBracket, RightAngleBracket } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../../../../../../editor/common/codecs/simpleCodec/parserBase.js'; +import { BaseToken } from '../base/baseToken.js'; +import { At } from '../base/simpleCodec/tokens/at.js'; +import { Tab } from '../base/simpleCodec/tokens/tab.js'; +import { Hash } from '../base/simpleCodec/tokens/hash.js'; +import { Space } from '../base/simpleCodec/tokens/space.js'; +import { Colon } from '../base/simpleCodec/tokens/colon.js'; +import { NewLine } from '../base/linesCodec/tokens/newLine.js'; +import { FormFeed } from '../base/simpleCodec/tokens/formFeed.js'; +import { VerticalTab } from '../base/simpleCodec/tokens/verticalTab.js'; +import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; +import { CarriageReturn } from '../base/linesCodec/tokens/carriageReturn.js'; +import { ExclamationMark } from '../base/simpleCodec/tokens/exclamationMark.js'; +import { LeftBracket, RightBracket } from '../base/simpleCodec/tokens/brackets.js'; +import { LeftAngleBracket, RightAngleBracket } from '../base/simpleCodec/tokens/angleBrackets.js'; +import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; /** * List of characters that terminate the prompt at-mention sequence. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts index ebd3f9a096c..557f5e74379 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts @@ -6,22 +6,22 @@ import { assert } from '../../../../../../../base/common/assert.js'; import { PromptSlashCommand } from '../tokens/promptSlashCommand.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; -import { At } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/at.js'; -import { Tab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/tab.js'; -import { Hash } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/hash.js'; -import { Slash } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/slash.js'; -import { Space } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/space.js'; -import { Colon } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/colon.js'; -import { NewLine } from '../../../../../../../editor/common/codecs/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../../../../../../../editor/common/codecs/simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../../../../../../../editor/common/codecs/linesCodec/tokens/carriageReturn.js'; -import { ExclamationMark } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/exclamationMark.js'; -import { LeftBracket, RightBracket } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/brackets.js'; -import { LeftAngleBracket, RightAngleBracket } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../../../../../../editor/common/codecs/simpleCodec/parserBase.js'; +import { BaseToken } from '../base/baseToken.js'; +import { At } from '../base/simpleCodec/tokens/at.js'; +import { Tab } from '../base/simpleCodec/tokens/tab.js'; +import { Hash } from '../base/simpleCodec/tokens/hash.js'; +import { Slash } from '../base/simpleCodec/tokens/slash.js'; +import { Space } from '../base/simpleCodec/tokens/space.js'; +import { Colon } from '../base/simpleCodec/tokens/colon.js'; +import { NewLine } from '../base/linesCodec/tokens/newLine.js'; +import { FormFeed } from '../base/simpleCodec/tokens/formFeed.js'; +import { VerticalTab } from '../base/simpleCodec/tokens/verticalTab.js'; +import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; +import { CarriageReturn } from '../base/linesCodec/tokens/carriageReturn.js'; +import { ExclamationMark } from '../base/simpleCodec/tokens/exclamationMark.js'; +import { LeftBracket, RightBracket } from '../base/simpleCodec/tokens/brackets.js'; +import { LeftAngleBracket, RightAngleBracket } from '../base/simpleCodec/tokens/angleBrackets.js'; +import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; /** * List of characters that terminate the prompt at-mention sequence. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts index c40a8c5622f..b97ad0fd479 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts @@ -5,10 +5,10 @@ import { assert } from '../../../../../../../base/common/assert.js'; import { PromptTemplateVariable } from '../tokens/promptTemplateVariable.js'; -import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; -import { TSimpleDecoderToken } from '../../../../../../../editor/common/codecs/simpleCodec/simpleDecoder.js'; -import { DollarSign, LeftCurlyBrace, RightCurlyBrace } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/index.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../../../../../../editor/common/codecs/simpleCodec/parserBase.js'; +import { BaseToken } from '../base/baseToken.js'; +import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; +import { DollarSign, LeftCurlyBrace, RightCurlyBrace } from '../base/simpleCodec/tokens/tokens.js'; +import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; /** * Parsers of the `${variable}` token sequence in a prompt text. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts index 376f251b2ff..ef68e78ec2d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts @@ -5,22 +5,22 @@ import { assert } from '../../../../../../../base/common/assert.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; +import { BaseToken } from '../base/baseToken.js'; import { PromptVariable, PromptVariableWithData } from '../tokens/promptVariable.js'; -import { At } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/at.js'; -import { Tab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/tab.js'; -import { Hash } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/hash.js'; -import { Space } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/space.js'; -import { Colon } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/colon.js'; -import { NewLine } from '../../../../../../../editor/common/codecs/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../../../../../../../editor/common/codecs/simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../../../../../../../editor/common/codecs/linesCodec/tokens/carriageReturn.js'; -import { ExclamationMark } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/exclamationMark.js'; -import { LeftBracket, RightBracket } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/brackets.js'; -import { LeftAngleBracket, RightAngleBracket } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../../../../../../editor/common/codecs/simpleCodec/parserBase.js'; +import { At } from '../base/simpleCodec/tokens/at.js'; +import { Tab } from '../base/simpleCodec/tokens/tab.js'; +import { Hash } from '../base/simpleCodec/tokens/hash.js'; +import { Space } from '../base/simpleCodec/tokens/space.js'; +import { Colon } from '../base/simpleCodec/tokens/colon.js'; +import { NewLine } from '../base/linesCodec/tokens/newLine.js'; +import { FormFeed } from '../base/simpleCodec/tokens/formFeed.js'; +import { VerticalTab } from '../base/simpleCodec/tokens/verticalTab.js'; +import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; +import { CarriageReturn } from '../base/linesCodec/tokens/carriageReturn.js'; +import { ExclamationMark } from '../base/simpleCodec/tokens/exclamationMark.js'; +import { LeftBracket, RightBracket } from '../base/simpleCodec/tokens/brackets.js'; +import { LeftAngleBracket, RightAngleBracket } from '../base/simpleCodec/tokens/angleBrackets.js'; +import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; /** * List of characters that terminate the prompt variable sequence. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts index 72d1b4259bc..1069d4a7a89 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { PromptToken } from './promptToken.js'; -import { assert } from '../../../../../../../base/common/assert.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { INVALID_NAME_CHARACTERS, STOP_CHARACTERS } from '../parsers/promptSlashCommandParser.js'; /** * All prompt at-mentions start with `/` character. @@ -24,14 +22,6 @@ export class PromptSlashCommand extends PromptToken { */ public readonly name: string, ) { - // sanity check of characters used in the provided command name - for (const character of name) { - assert( - (INVALID_NAME_CHARACTERS.includes(character) === false) && - (STOP_CHARACTERS.includes(character) === false), - `Slash command 'name' cannot contain character '${character}', got '${name}'.`, - ); - } super(range); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts index a50642ef526..be3051f2c88 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts @@ -5,8 +5,8 @@ import { PromptToken } from './promptToken.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { DollarSign } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/dollarSign.js'; -import { LeftCurlyBrace, RightCurlyBrace } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/curlyBraces.js'; +import { DollarSign } from '../base/simpleCodec/tokens/dollarSign.js'; +import { LeftCurlyBrace, RightCurlyBrace } from '../base/simpleCodec/tokens/curlyBraces.js'; /** * Represents a `${variable}` token in a prompt text. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts index 38a9d69d5ac..021b8d5a425 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; +import { BaseToken } from '../base/baseToken.js'; /** * Common base token that all chatbot `prompt` tokens should inherit from. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts index c84903e544f..ec5dd2e7a36 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { PromptToken } from './promptToken.js'; -import { assert } from '../../../../../../../base/common/assert.js'; import { IRange, Range } from '../../../../../../../editor/common/core/range.js'; -import { INVALID_NAME_CHARACTERS, STOP_CHARACTERS } from '../parsers/promptVariableParser.js'; /** * All prompt variables start with `#` character. @@ -29,14 +27,6 @@ export class PromptVariable extends PromptToken { */ public readonly name: string, ) { - // sanity check of characters used in the provided variable name - for (const character of name) { - assert( - (INVALID_NAME_CHARACTERS.includes(character) === false) && - (STOP_CHARACTERS.includes(character) === false), - `Variable 'name' cannot contain character '${character}', got '${name}'.`, - ); - } super(range); } @@ -74,14 +64,6 @@ export class PromptVariableWithData extends PromptVariable { public readonly data: string, ) { super(fullRange, name); - - // sanity check of characters used in the provided variable data - for (const character of data) { - assert( - (STOP_CHARACTERS.includes(character) === false), - `Variable 'data' cannot contain character '${character}', got '${data}'.`, - ); - } } /** diff --git a/src/vs/platform/prompts/common/config.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts similarity index 65% rename from src/vs/platform/prompts/common/config.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts index b5c8ad3494d..e1ee3ea8a2a 100644 --- a/src/vs/platform/prompts/common/config.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextKeyExpr } from '../../contextkey/common/contextkey.js'; -import type { IConfigurationService } from '../../configuration/common/configuration.js'; -import { CONFIG_KEY, PROMPT_DEFAULT_SOURCE_FOLDER, INSTRUCTIONS_LOCATIONS_CONFIG_KEY, PROMPT_LOCATIONS_CONFIG_KEY, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, PromptsType, getPromptFileDefaultLocation, MODE_LOCATIONS_CONFIG_KEY, getPromptFileLocationsConfigKey } from './prompts.js'; +import type { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { PromptsType } from '../promptTypes.js'; +import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, getPromptFileDefaultLocation } from './promptFileLocations.js'; /** * Configuration helper for the `reusable prompts` feature. - * @see {@link CONFIG_KEY}, {@link PROMPT_LOCATIONS_CONFIG_KEY}, {@link INSTRUCTIONS_LOCATIONS_CONFIG_KEY} or {@link MODE_LOCATIONS_CONFIG_KEY}. + * @see {@link PromptsConfig.KEY}, {@link PromptsConfig.PROMPT_LOCATIONS_KEY}, {@link PromptsConfig.INSTRUCTIONS_LOCATION_KEY} or {@link PromptsConfig.MODE_LOCATION_KEY}. * * ### Functions * @@ -28,36 +29,46 @@ import { CONFIG_KEY, PROMPT_DEFAULT_SOURCE_FOLDER, INSTRUCTIONS_LOCATIONS_CONFIG * - current root folder (if a single folder is open) */ export namespace PromptsConfig { - export const KEY = CONFIG_KEY; - export const PROMPT_LOCATIONS_KEY = PROMPT_LOCATIONS_CONFIG_KEY; - export const INSTRUCTIONS_LOCATION_KEY = INSTRUCTIONS_LOCATIONS_CONFIG_KEY; - export const MODE_LOCATION_KEY = MODE_LOCATIONS_CONFIG_KEY; + /** + * Configuration key for the `reusable prompts` feature + * (also known as `prompt files`, `prompt instructions`, etc.). + */ + export const KEY = 'chat.promptFiles'; + + /** + * Configuration key for the locations of reusable prompt files. + */ + export const PROMPT_LOCATIONS_KEY = 'chat.promptFilesLocations'; + + /** + * Configuration key for the locations of instructions files. + */ + export const INSTRUCTIONS_LOCATION_KEY = 'chat.instructionsFilesLocations'; + /** + * Configuration key for the locations of mode files. + */ + export const MODE_LOCATION_KEY = 'chat.modeFilesLocations'; /** * Checks if the feature is enabled. - * @see {@link CONFIG_KEY}. + * @see {@link PromptsConfig.KEY}. */ - export const enabled = ( - configService: IConfigurationService, - ): boolean => { - const enabledValue = configService.getValue(CONFIG_KEY); + export function enabled(configService: IConfigurationService): boolean { + const enabledValue = configService.getValue(PromptsConfig.KEY); return asBoolean(enabledValue) ?? false; - }; + } /** * Context key expression for the `reusable prompts` feature `enabled` status. */ - export const enabledCtx = ContextKeyExpr.equals(`config.${CONFIG_KEY}`, true); + export const enabledCtx = ContextKeyExpr.equals(`config.${PromptsConfig.KEY}`, true); /** * Get value of the `reusable prompt locations` configuration setting. * @see {@link PROMPT_LOCATIONS_CONFIG_KEY}, {@link INSTRUCTIONS_LOCATIONS_CONFIG_KEY}, {@link MODE_LOCATIONS_CONFIG_KEY}. */ - export const getLocationsValue = ( - configService: IConfigurationService, - type: PromptsType - ): Record | undefined => { + export function getLocationsValue(configService: IConfigurationService, type: PromptsType): Record | undefined { const key = getPromptFileLocationsConfigKey(type); const configValue = configService.getValue(key); @@ -85,16 +96,13 @@ export namespace PromptsConfig { } return undefined; - }; + } /** * Gets list of source folders for prompt files. * Defaults to {@link PROMPT_DEFAULT_SOURCE_FOLDER}, {@link INSTRUCTIONS_DEFAULT_SOURCE_FOLDER} or {@link MODE_DEFAULT_SOURCE_FOLDER}. */ - export const promptSourceFolders = ( - configService: IConfigurationService, - type: PromptsType - ): string[] => { + export function promptSourceFolders(configService: IConfigurationService, type: PromptsType): string[] { const value = getLocationsValue(configService, type); const defaultSourceFolder = getPromptFileDefaultLocation(type); @@ -122,7 +130,21 @@ export namespace PromptsConfig { // `undefined`, `null`, and `false` cases return []; - }; + } + +} + +export function getPromptFileLocationsConfigKey(type: PromptsType): string { + switch (type) { + case PromptsType.instructions: + return PromptsConfig.INSTRUCTIONS_LOCATION_KEY; + case PromptsType.prompt: + return PromptsConfig.PROMPT_LOCATIONS_KEY; + case PromptsType.mode: + return PromptsConfig.MODE_LOCATION_KEY; + default: + throw new Error('Unknown prompt type'); + } } /** @@ -133,7 +155,7 @@ export namespace PromptsConfig { * be clearly mapped to a boolean (e.g., `"true"`, `"TRUE"`, `"FaLSe"`, etc.), * `undefined` for rest of the values */ -export const asBoolean = (value: unknown): boolean | undefined => { +export function asBoolean(value: unknown): boolean | undefined { if (typeof value === 'boolean') { return value; } @@ -152,4 +174,4 @@ export const asBoolean = (value: unknown): boolean | undefined => { } return undefined; -}; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/configMigration.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/configMigration.ts similarity index 83% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/configMigration.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/config/configMigration.ts index f52d6269f8e..38d47d6dd1c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/configMigration.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/configMigration.ts @@ -5,10 +5,9 @@ import { assert } from '../../../../../../base/common/assert.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { asBoolean } from '../../../../../../platform/prompts/common/config.js'; +import { asBoolean, PromptsConfig } from './config.js'; import { IWorkbenchContribution } from '../../../../../common/contributions.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { CONFIG_KEY, PROMPT_LOCATIONS_CONFIG_KEY } from '../../../../../../platform/prompts/common/prompts.js'; /** * Contribution that migrates the old config setting value to a new one. @@ -31,7 +30,7 @@ export class ConfigMigration implements IWorkbenchContribution { * The main function that implements the migration logic. */ private async migrateConfig(): Promise { - const value = await this.configService.getValue(CONFIG_KEY); + const value = await this.configService.getValue(PromptsConfig.KEY); // if setting is not set, nothing to do if ((value === undefined) || (value === null)) { @@ -62,8 +61,8 @@ export class ConfigMigration implements IWorkbenchContribution { locationsValue[trimmedValue] = true; } - await this.configService.updateValue(CONFIG_KEY, true); - await this.configService.updateValue(PROMPT_LOCATIONS_CONFIG_KEY, locationsValue); + await this.configService.updateValue(PromptsConfig.KEY, true); + await this.configService.updateValue(PromptsConfig.PROMPT_LOCATIONS_KEY, locationsValue); return; } @@ -95,8 +94,8 @@ export class ConfigMigration implements IWorkbenchContribution { locationsValue[trimmedValue] = enabled; } - await this.configService.updateValue(CONFIG_KEY, true); - await this.configService.updateValue(PROMPT_LOCATIONS_CONFIG_KEY, locationsValue); + await this.configService.updateValue(PromptsConfig.KEY, true); + await this.configService.updateValue(PromptsConfig.PROMPT_LOCATIONS_KEY, locationsValue); return; } @@ -112,8 +111,8 @@ export class ConfigMigration implements IWorkbenchContribution { `String value must not be a boolean, got '${value}'.`, ); - await this.configService.updateValue(CONFIG_KEY, true); - await this.configService.updateValue(PROMPT_LOCATIONS_CONFIG_KEY, { [value]: true }); + await this.configService.updateValue(PromptsConfig.KEY, true); + await this.configService.updateValue(PromptsConfig.PROMPT_LOCATIONS_KEY, { [value]: true }); return; } } diff --git a/src/vs/platform/prompts/common/prompts.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts similarity index 66% rename from src/vs/platform/prompts/common/prompts.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts index 620a2109b7b..fbdf2df20aa 100644 --- a/src/vs/platform/prompts/common/prompts.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts @@ -3,21 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from '../../../base/common/uri.js'; -import { basename } from '../../../base/common/path.js'; - -/** - * What the prompt is used for. - */ -export enum PromptsType { - instructions = 'instructions', - prompt = 'prompt', - mode = 'mode' -} - -export function isValidPromptType(type: string): type is PromptsType { - return Object.values(PromptsType).includes(type as PromptsType); -} +import { URI } from '../../../../../../base/common/uri.js'; +import { basename } from '../../../../../../base/common/path.js'; +import { PromptsType } from '../promptTypes.js'; /** * File extension for the reusable prompt files. @@ -39,26 +27,6 @@ export const MODE_FILE_EXTENSION = '.chatmode.md'; */ export const COPILOT_CUSTOM_INSTRUCTIONS_FILENAME = 'copilot-instructions.md'; -/** - * Configuration key for the `reusable prompts` feature - * (also known as `prompt files`, `prompt instructions`, etc.). - */ -export const CONFIG_KEY: string = 'chat.promptFiles'; - -/** - * Configuration key for the locations of reusable prompt files. - */ -export const PROMPT_LOCATIONS_CONFIG_KEY: string = 'chat.promptFilesLocations'; - -/** - * Configuration key for the locations of instructions files. - */ -export const INSTRUCTIONS_LOCATIONS_CONFIG_KEY: string = 'chat.instructionsFilesLocations'; - -/** - * Configuration key for the locations of mode files. - */ -export const MODE_LOCATIONS_CONFIG_KEY: string = 'chat.modeFilesLocations'; /** * Default reusable prompt files source folder. @@ -129,35 +97,11 @@ export function getPromptFileDefaultLocation(type: PromptsType): string { } } -export function getPromptFileLocationsConfigKey(type: PromptsType): string { - switch (type) { - case PromptsType.instructions: - return INSTRUCTIONS_LOCATIONS_CONFIG_KEY; - case PromptsType.prompt: - return PROMPT_LOCATIONS_CONFIG_KEY; - case PromptsType.mode: - return MODE_LOCATIONS_CONFIG_KEY; - default: - throw new Error('Unknown prompt type'); - } -} - - -/** - * Check whether provided URI belongs to an `untitled` document. - */ -export const isUntitled = ( - fileUri: URI, -): boolean => { - return fileUri.scheme === 'untitled'; -}; /** * Gets clean prompt name without file extension. */ -export const getCleanPromptName = ( - fileUri: URI, -): string => { +export function getCleanPromptName(fileUri: URI): string { const fileName = basename(fileUri.path); const extensions = [ @@ -181,4 +125,4 @@ export const getCleanPromptName = ( // to account for that, we return the full file name including the file // extension for all other cases return basename(fileUri.path); -}; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts index 9b8774efccb..831fd7cbe49 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PROMPT_LANGUAGE_ID } from '../constants.js'; +import { PROMPT_LANGUAGE_ID } from '../promptTypes.js'; import { IPromptContentsProvider } from './types.js'; import { URI } from '../../../../../../base/common/uri.js'; import { assert } from '../../../../../../base/common/assert.js'; @@ -12,7 +12,7 @@ import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js' import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; -import { isPromptOrInstructionsFile } from '../../../../../../platform/prompts/common/prompts.js'; +import { isPromptOrInstructionsFile } from '../config/promptFileLocations.js'; import { IPromptContentsProviderOptions, PromptContentsProviderBase } from './promptContentsProviderBase.js'; import { OpenFailed, NotPromptFile, ResolveError, FolderReference } from '../../promptFileReferenceErrors.js'; import { FileChangesEvent, FileChangeType, IFileService } from '../../../../../../platform/files/common/files.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts index 0a67109dcec..1f90d5ff59d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptContentsProvider } from './types.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { Emitter } from '../../../../../../base/common/event.js'; import { assert } from '../../../../../../base/common/assert.js'; -import { CancellationError } from '../../../../../../base/common/errors.js'; import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; -import { ObservableDisposable } from '../../../../../../base/common/observableDisposable.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../constants.js'; -import { FailedToResolveContentsStream, ResolveError } from '../../promptFileReferenceErrors.js'; import { cancelPreviousCalls } from '../../../../../../base/common/decorators/cancelPreviousCalls.js'; +import { CancellationError } from '../../../../../../base/common/errors.js'; +import { Emitter } from '../../../../../../base/common/event.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { FailedToResolveContentsStream, ResolveError } from '../../promptFileReferenceErrors.js'; +import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { ObservableDisposable } from '../utils/observableDisposable.js'; +import { IPromptContentsProvider } from './types.js'; /** * Options of the {@link PromptContentsProviderBase} class. @@ -180,14 +179,14 @@ export abstract class PromptContentsProviderBase< /** * Start producing the prompt contents data. */ - public start(): this { + public start(token?: CancellationToken): this { assert( !this.isDisposed, 'Cannot start contents provider that was already disposed.', ); // `'full'` means "everything has changed" - this.onContentsChanged('full'); + this.onContentsChanged('full', token); // subscribe to the change event emitted by a child class this._register(this.onChangeEmitter.event(this.onContentsChanged, this)); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts index 47f4ae66330..cef4a267200 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptContentsProvider } from './types.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { FilePromptContentProvider } from './filePromptContentsProvider.js'; -import { TextModel } from '../../../../../../editor/common/model/textModel.js'; import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { TextModel } from '../../../../../../editor/common/model/textModel.js'; import { IModelContentChangedEvent } from '../../../../../../editor/common/textModelEvents.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { objectStreamFromTextModel } from '../codecs/base/utils/objectStreamFromTextModel.js'; +import { FilePromptContentProvider } from './filePromptContentsProvider.js'; import { IPromptContentsProviderOptions, PromptContentsProviderBase } from './promptContentsProviderBase.js'; -import { objectStreamFromTextModel } from '../../../../../../editor/common/codecs/utils/objectStreamFromTextModel.js'; +import { IPromptContentsProvider } from './types.js'; /** * Prompt contents provider for a {@link ITextModel} instance. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts index a99407f610e..bec215b1d04 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts @@ -8,7 +8,8 @@ import { Event } from '../../../../../../base/common/event.js'; import { ResolveError } from '../../promptFileReferenceErrors.js'; import { IDisposable } from '../../../../../../base/common/lifecycle.js'; import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; -import { PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; +import { PromptsType } from '../promptTypes.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; /** * Interface for a prompt contents provider. Prompt contents providers are @@ -56,7 +57,7 @@ export interface IPromptContentsProvider extends IDisposable { /** * Start the contents provider to produce the underlying contents. */ - start(): this; + start(token?: CancellationToken): this; /** * Create a new instance of prompt contents provider. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/index.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/index.ts deleted file mode 100644 index 538fb25ab05..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ConfigMigration } from './configMigration.js'; -import { LANGUAGE_FEATURE_CONTRIBUTIONS } from './languageFeatures/index.js'; -import { Registry } from '../../../../../../platform/registry/common/platform.js'; -import { LifecyclePhase } from '../../../../../services/lifecycle/common/lifecycle.js'; -import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } from '../../../../../common/contributions.js'; - -/** - * Function that registers all prompt-file related contributions. - */ -export const registerPromptFileContributions = (): void => { - registerContributions(LANGUAGE_FEATURE_CONTRIBUTIONS); - registerContribution(ConfigMigration); -}; - -/** - * Type for a generic workbench contribution. - */ -export type TContribution = new (...args: any[]) => IWorkbenchContribution; - -/** - * Register a specific workbench contribution. - */ -const registerContribution = (contribution: TContribution): void => { - Registry.as(Extensions.Workbench) - .registerWorkbenchContribution(contribution, LifecyclePhase.Eventually); -}; - -/** - * Register a specific workbench contribution. - */ -const registerContributions = (contributions: readonly TContribution[]): void => { - contributions - .forEach(registerContribution); -}; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/index.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/index.ts deleted file mode 100644 index 05481cc7058..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TContribution } from '../index.js'; -import { PromptLinkProvider } from './providers/promptLinkProvider.js'; -import { isWindows } from '../../../../../../../base/common/platform.js'; -import { PromptPathAutocompletion } from './providers/promptPathAutocompletion.js'; -import { PromptLinkDiagnosticsInstanceManager } from './providers/promptLinkDiagnosticsProvider.js'; -import { PromptHeaderDiagnosticsInstanceManager } from './providers/promptHeaderDiagnosticsProvider.js'; - -/** - * Base list of language feature contributions. - */ -const CONTRIBUTIONS: TContribution[] = [ - PromptLinkProvider, - PromptLinkDiagnosticsInstanceManager, - PromptHeaderDiagnosticsInstanceManager, - /** - * PromptDecorationsProviderInstanceManager is currently disabled because the only currently - * available decoration is the Front Matter header, which we decided to disable for now. - * Add it back when more decorations are needed. - */ - // PromptDecorationsProviderInstanceManager, -]; - -/** - * We restrict this provider to `Unix` machines for now because of - * the filesystem paths differences on `Windows` operating system. - * - * Notes on `Windows` support: - * - we add the `./` for the first path component, which may not work on `Windows` - * - the first path component of the absolute paths must be a drive letter - */ -if (isWindows === false) { - CONTRIBUTIONS.push(PromptPathAutocompletion); -} - -/** - * List of language feature contributions for the prompt files. - */ -export const LANGUAGE_FEATURE_CONTRIBUTIONS = Object.freeze(CONTRIBUTIONS); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/index.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/index.ts deleted file mode 100644 index 3980294fc1a..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ColorIdentifier } from '../../../../../../../../../../../platform/theme/common/colorUtils.js'; - -/** - * Convert a registered color to a CSS variable string. - */ -export const asCssVariable = (color: ColorIdentifier): string => { - return `var(--vscode-${color.replaceAll('.', '-')})`; -}; - -export type * from './types.js'; -export { DecorationBase, type TDecorationClass } from './decorationBase.js'; -export { ReactiveDecorationBase, type TChangedDecorator } from './reactiveDecorationBase.js'; - diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/frontMatterDecoration.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts similarity index 83% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/frontMatterDecoration.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts index 23ec581b6b8..d07720eff5b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/frontMatterDecoration.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Position } from '../../../../../../../../editor/common/core/position.js'; +import { localize } from '../../../../../../../../nls.js'; +import { contrastBorder, editorBackground } from '../../../../../../../../platform/theme/common/colorRegistry.js'; +import { asCssVariable, ColorIdentifier, darken, registerColor } from '../../../../../../../../platform/theme/common/colorUtils.js'; +import { BaseToken } from '../../../codecs/base/baseToken.js'; +import { FrontMatterHeader } from '../../../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; import { CssClassModifiers } from '../types.js'; -import { localize } from '../../../../../../../../../../nls.js'; import { FrontMatterMarkerDecoration } from './frontMatterMarkerDecoration.js'; -import { Position } from '../../../../../../../../../../editor/common/core/position.js'; -import { BaseToken } from '../../../../../../../../../../editor/common/codecs/baseToken.js'; -import { contrastBorder, editorBackground } from '../../../../../../../../../../platform/theme/common/colorRegistry.js'; -import { ColorIdentifier, darken, registerColor } from '../../../../../../../../../../platform/theme/common/colorUtils.js'; -import { TAddAccessor, TDecorationStyles, ReactiveDecorationBase, asCssVariable, IReactiveDecorationClassNames } from './utils/index.js'; -import { FrontMatterHeader } from '../../../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js'; +import { ReactiveDecorationBase } from './utils/reactiveDecorationBase.js'; +import { IReactiveDecorationClassNames, TAddAccessor, TDecorationStyles } from './utils/types.js'; /** * Decoration CSS class names. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/frontMatterMarkerDecoration.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts similarity index 85% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/frontMatterMarkerDecoration.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts index 70e82996729..2a4a174c9f4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/frontMatterMarkerDecoration.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { CssClassModifiers } from '../types.js'; -import { TDecorationStyles, ReactiveDecorationBase, IReactiveDecorationClassNames } from './utils/index.js'; -import { FrontMatterMarker } from '../../../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterMarker.js'; +import { TDecorationStyles, IReactiveDecorationClassNames } from './utils/types.js'; +import { FrontMatterMarker } from '../../../codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.js'; +import { ReactiveDecorationBase } from './utils/reactiveDecorationBase.js'; /** * Decoration CSS class names. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/decorationBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts similarity index 89% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/decorationBase.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts index 614a22a7ce9..8f28ec33247 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/decorationBase.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Range } from '../../../../../../../../../../../editor/common/core/range.js'; -import { IMarkdownString } from '../../../../../../../../../../../base/common/htmlContent.js'; -import { BaseToken } from '../../../../../../../../../../../editor/common/codecs/baseToken.js'; -import { TrackedRangeStickiness } from '../../../../../../../../../../../editor/common/model.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { IMarkdownString } from '../../../../../../../../../base/common/htmlContent.js'; +import { BaseToken } from '../../../../codecs/base/baseToken.js'; +import { TrackedRangeStickiness } from '../../../../../../../../../editor/common/model.js'; import type { TAddAccessor, TChangeAccessor, TDecorationStyles, TRemoveAccessor } from './types.js'; -import { ModelDecorationOptions } from '../../../../../../../../../../../editor/common/model/textModel.js'; +import { ModelDecorationOptions } from '../../../../../../../../../editor/common/model/textModel.js'; /** * Base class for all editor decorations. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/reactiveDecorationBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts similarity index 95% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/reactiveDecorationBase.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts index c354526150c..42533aea5fe 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/reactiveDecorationBase.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { DecorationBase } from './decorationBase.js'; -import { Position } from '../../../../../../../../../../../editor/common/core/position.js'; -import { BaseToken } from '../../../../../../../../../../../editor/common/codecs/baseToken.js'; +import { Position } from '../../../../../../../../../editor/common/core/position.js'; +import { BaseToken } from '../../../../codecs/base/baseToken.js'; import type { IReactiveDecorationClassNames, TAddAccessor, TChangeAccessor, TRemoveAccessor } from './types.js'; /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts similarity index 97% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/types.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts index 0f5aaee01a2..1ca7e270d3f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/decorations/utils/types.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IModelDecorationsChangeAccessor } from '../../../../../../../../../../../editor/common/model.js'; +import { IModelDecorationsChangeAccessor } from '../../../../../../../../../editor/common/model.js'; /** * CSS class names of a `reactive` decoration. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/promptDecorationsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts similarity index 87% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/promptDecorationsProvider.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts index ef367348515..ce87f6accb8 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/promptDecorationsProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptsService } from '../../../../service/types.js'; +import { IPromptsService } from '../../service/promptsService.js'; import { ProviderInstanceBase } from '../providerInstanceBase.js'; -import { ITextModel } from '../../../../../../../../../editor/common/model.js'; +import { ITextModel } from '../../../../../../../editor/common/model.js'; import { FrontMatterDecoration } from './decorations/frontMatterDecoration.js'; -import { toDisposable } from '../../../../../../../../../base/common/lifecycle.js'; -import { Position } from '../../../../../../../../../editor/common/core/position.js'; -import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js'; +import { toDisposable } from '../../../../../../../base/common/lifecycle.js'; +import { Position } from '../../../../../../../editor/common/core/position.js'; +import { BaseToken } from '../../codecs/base/baseToken.js'; import { ProviderInstanceManagerBase, TProviderClass } from '../providerInstanceManagerBase.js'; -import { registerThemingParticipant } from '../../../../../../../../../platform/theme/common/themeService.js'; -import { DecorationBase, ReactiveDecorationBase, type TDecorationClass, type TChangedDecorator } from './decorations/utils/index.js'; -import { FrontMatterHeader } from '../../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js'; +import { registerThemingParticipant } from '../../../../../../../platform/theme/common/themeService.js'; +import { FrontMatterHeader } from '../../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; +import { ReactiveDecorationBase, TChangedDecorator } from './decorations/utils/reactiveDecorationBase.js'; +import { DecorationBase, TDecorationClass } from './decorations/utils/decorationBase.js'; /** * Prompt tokens that are decorated by this provider. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts similarity index 85% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/types.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts index 366a0c33150..fb3ef81b122 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/decorationsProvider/types.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRange } from '../../../../../../../../../editor/common/core/range.js'; -import { ModelDecorationOptions } from '../../../../../../../../../editor/common/model/textModel.js'; +import { IRange } from '../../../../../../../editor/common/core/range.js'; +import { ModelDecorationOptions } from '../../../../../../../editor/common/model/textModel.js'; /** * Decoration object. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptHeaderDiagnosticsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts similarity index 86% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptHeaderDiagnosticsProvider.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts index 5500f832d49..8ff0daf07b4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptHeaderDiagnosticsProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptsService } from '../../../service/types.js'; +import { IPromptsService } from '../service/promptsService.js'; import { ProviderInstanceBase } from './providerInstanceBase.js'; -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { assertNever } from '../../../../../../../../base/common/assert.js'; -import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { assertNever } from '../../../../../../base/common/assert.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { ProviderInstanceManagerBase, TProviderClass } from './providerInstanceManagerBase.js'; -import { TDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../../../parsers/promptHeader/diagnostics.js'; -import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../../../platform/markers/common/markers.js'; +import { TDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../parsers/promptHeader/diagnostics.js'; +import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; /** * Unique ID of the markers provider class. @@ -80,9 +80,7 @@ class PromptHeaderDiagnosticsProvider extends ProviderInstanceBase { /** * Convert a provided diagnostic object into a marker data object. */ -const toMarker = ( - diagnostic: TDiagnostic, -): IMarkerData => { +function toMarker(diagnostic: TDiagnostic): IMarkerData { if (diagnostic instanceof PromptMetadataWarning) { return { message: diagnostic.message, @@ -103,7 +101,7 @@ const toMarker = ( diagnostic, `Unknown prompt metadata diagnostic type '${diagnostic}'.`, ); -}; +} /** * The class that manages creation and disposal of {@link PromptHeaderDiagnosticsProvider} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptLinkDiagnosticsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts similarity index 86% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptLinkDiagnosticsProvider.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts index 1d43958165f..0c7dab432bd 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptLinkDiagnosticsProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptsService } from '../../../service/types.js'; -import { IPromptFileReference } from '../../../parsers/types.js'; +import { IPromptsService } from '../service/promptsService.js'; +import { IPromptFileReference } from '../parsers/types.js'; import { ProviderInstanceBase } from './providerInstanceBase.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { NotPromptFile } from '../../../../promptFileReferenceErrors.js'; -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { assertDefined } from '../../../../../../../../base/common/types.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { NotPromptFile } from '../../promptFileReferenceErrors.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { assertDefined } from '../../../../../../base/common/types.js'; import { ProviderInstanceManagerBase, TProviderClass } from './providerInstanceManagerBase.js'; -import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../../../platform/markers/common/markers.js'; +import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; /** * Unique ID of the markers provider class. @@ -83,9 +83,7 @@ class PromptLinkDiagnosticsProvider extends ProviderInstanceBase { * - if the original error is of `NotPromptFile` type - we don't want to * show diagnostic markers for non-prompt file links in the prompts */ -const toMarker = ( - link: IPromptFileReference, -): IMarkerData => { +function toMarker(link: IPromptFileReference): IMarkerData { const { topError, linkRange } = link; // a sanity check because this function must be @@ -115,7 +113,7 @@ const toMarker = ( severity, ...linkRange, }; -}; +} /** * The class that manages creation and disposal of {@link PromptLinkDiagnosticsProvider} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts similarity index 63% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptLinkProvider.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts index c71d5fa387d..a92e0695b28 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts @@ -3,29 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptsService } from '../../../service/types.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { assertDefined } from '../../../../../../../../base/common/types.js'; -import { Disposable } from '../../../../../../../../base/common/lifecycle.js'; -import { CancellationError } from '../../../../../../../../base/common/errors.js'; -import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../../../constants.js'; -import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; -import { FolderReference, NotPromptFile } from '../../../../promptFileReferenceErrors.js'; -import { ILink, ILinksList, LinkProvider } from '../../../../../../../../editor/common/languages.js'; -import { ILanguageFeaturesService } from '../../../../../../../../editor/common/services/languageFeatures.js'; +import { IPromptsService } from '../service/promptsService.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { assertDefined } from '../../../../../../base/common/types.js'; +import { CancellationError } from '../../../../../../base/common/errors.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { FolderReference, NotPromptFile } from '../../promptFileReferenceErrors.js'; +import { ILink, ILinksList, LinkProvider } from '../../../../../../editor/common/languages.js'; /** * Provides link references for prompt files. */ -export class PromptLinkProvider extends Disposable implements LinkProvider { +export class PromptLinkProvider implements LinkProvider { constructor( @IPromptsService private readonly promptsService: IPromptsService, - @ILanguageFeaturesService private readonly languageService: ILanguageFeaturesService, ) { - super(); - - this._register(this.languageService.linkProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, this)); } /** @@ -49,7 +42,7 @@ export class PromptLinkProvider extends Disposable implements LinkProvider { // start the parser in case it was not started yet, // and wait for it to settle to a final result const { references } = await parser - .start() + .start(token) .settled(); // validate that the cancellation was not yet requested diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptPathAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptPathAutocompletion.ts similarity index 88% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptPathAutocompletion.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptPathAutocompletion.ts index a9849e899d9..8470df895c9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/promptPathAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptPathAutocompletion.ts @@ -14,21 +14,21 @@ * - add `Windows` support */ -import { IPromptsService } from '../../../service/types.js'; -import { URI } from '../../../../../../../../base/common/uri.js'; -import { isOneOf } from '../../../../../../../../base/common/types.js'; -import { extUri } from '../../../../../../../../base/common/resources.js'; -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { Disposable } from '../../../../../../../../base/common/lifecycle.js'; -import { CancellationError } from '../../../../../../../../base/common/errors.js'; -import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../../../constants.js'; -import { Position } from '../../../../../../../../editor/common/core/position.js'; -import { IPromptFileReference, TPromptReference } from '../../../parsers/types.js'; -import { assert, assertNever } from '../../../../../../../../base/common/assert.js'; -import { IFileService } from '../../../../../../../../platform/files/common/files.js'; -import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; -import { ILanguageFeaturesService } from '../../../../../../../../editor/common/services/languageFeatures.js'; -import { CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList } from '../../../../../../../../editor/common/languages.js'; +import { IPromptsService } from '../service/promptsService.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { isOneOf } from '../../../../../../base/common/types.js'; +import { extUri } from '../../../../../../base/common/resources.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { CancellationError } from '../../../../../../base/common/errors.js'; +import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../promptTypes.js'; +import { Position } from '../../../../../../editor/common/core/position.js'; +import { IPromptFileReference, TPromptReference } from '../parsers/types.js'; +import { assert, assertNever } from '../../../../../../base/common/assert.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js'; +import { CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList } from '../../../../../../editor/common/languages.js'; /** * Type for a filesystem completion item - the one that has its {@link CompletionItem.kind kind} set @@ -51,10 +51,7 @@ type TTriggerCharacter = ':' | '.' | '/'; /** * Finds a file reference that suites the provided `position`. */ -const findFileReference = ( - references: readonly TPromptReference[], - position: Position, -): IPromptFileReference | undefined => { +function findFileReference(references: readonly TPromptReference[], position: Position): IPromptFileReference | undefined { for (const reference of references) { const { range } = reference; @@ -78,7 +75,7 @@ const findFileReference = ( } return undefined; -}; +} /** * Provides reference paths autocompletion for the `#file:` variables inside prompts. @@ -141,7 +138,7 @@ export class PromptPathAutocompletion extends Disposable implements CompletionIt // start the parser in case it was not started yet, // and wait for it to settle to a final result const { references } = await parser - .start() + .start(token) .settled(); // validate that the cancellation was not yet requested diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/providerInstanceBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts similarity index 83% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/providerInstanceBase.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts index 52f2f81a81a..8742fef0a7c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/providerInstanceBase.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPromptsService, TSharedPrompt } from '../../../service/types.js'; -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { ObservableDisposable } from '../../../../../../../../base/common/observableDisposable.js'; -import { CancellationToken, CancellationTokenSource } from '../../../../../../../../base/common/cancellation.js'; +import { IPromptsService, TSharedPrompt } from '../service/promptsService.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { ObservableDisposable } from '../utils/observableDisposable.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../../../base/common/cancellation.js'; /** * Abstract base class for all reusable prompt file providers. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/providerInstanceManagerBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts similarity index 80% rename from src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/providerInstanceManagerBase.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts index 905b3bdc1f3..36ff2339b49 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contributions/languageFeatures/providers/providerInstanceManagerBase.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { ProviderInstanceBase } from './providerInstanceBase.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { assertDefined } from '../../../../../../../../base/common/types.js'; -import { Disposable } from '../../../../../../../../base/common/lifecycle.js'; -import { ObjectCache } from '../../../../../../../../base/common/objectCache.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../../constants.js'; -import { IModelService } from '../../../../../../../../editor/common/services/model.js'; -import { PromptsConfig } from '../../../../../../../../platform/prompts/common/config.js'; -import { IEditorService } from '../../../../../../../services/editor/common/editorService.js'; -import { IDiffEditor, IEditor, IEditorModel } from '../../../../../../../../editor/common/editorCommon.js'; -import { IInstantiationService } from '../../../../../../../../platform/instantiation/common/instantiation.js'; -import { IConfigurationService } from '../../../../../../../../platform/configuration/common/configuration.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { assertDefined } from '../../../../../../base/common/types.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { ObjectCache } from '../utils/objectCache.js'; +import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../promptTypes.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; +import { PromptsConfig } from '../config/config.js'; +import { IEditorService } from '../../../../../services/editor/common/editorService.js'; +import { IDiffEditor, IEditor, IEditorModel } from '../../../../../../editor/common/editorCommon.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; /** * Type for a text editor that is used for reusable prompt files. @@ -147,22 +147,18 @@ export abstract class ProviderInstanceManagerBase { +function isPromptFile(languageId: string): boolean { return [ PROMPT_LANGUAGE_ID, INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, ].includes(languageId); -}; +} /** * Check if a provided model is used for prompt files. */ -const isPromptFileModel = ( - model: IEditorModel, -): model is ITextModel => { +function isPromptFileModel(model: IEditorModel): model is ITextModel { // we support only `text editors` for now so filter out `diff` ones if ('modified' in model || 'model' in model) { return false; @@ -177,4 +173,4 @@ const isPromptFileModel = ( } return true; -}; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts index 6353799465d..7be72d8379f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts @@ -24,21 +24,22 @@ import type { TPromptReference, IResolveError, ITopError } from './types.js'; import { type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { assert, assertNever } from '../../../../../../base/common/assert.js'; import { basename, dirname } from '../../../../../../base/common/resources.js'; -import { BaseToken } from '../../../../../../editor/common/codecs/baseToken.js'; +import { BaseToken } from '../codecs/base/baseToken.js'; import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; import { type IRange, Range } from '../../../../../../editor/common/core/range.js'; import { PromptHeader, type TPromptMetadata } from './promptHeader/promptHeader.js'; -import { ObservableDisposable } from '../../../../../../base/common/observableDisposable.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../constants.js'; -import { LinesDecoder } from '../../../../../../editor/common/codecs/linesCodec/linesDecoder.js'; +import { ObservableDisposable } from '../utils/observableDisposable.js'; +import { PromptsType, INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../promptTypes.js'; +import { LinesDecoder } from '../codecs/base/linesCodec/linesDecoder.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { MarkdownLink } from '../../../../../../editor/common/codecs/markdownCodec/tokens/markdownLink.js'; -import { MarkdownToken } from '../../../../../../editor/common/codecs/markdownCodec/tokens/markdownToken.js'; -import { isPromptOrInstructionsFile, PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; -import { FrontMatterHeader } from '../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js'; +import { MarkdownLink } from '../codecs/base/markdownCodec/tokens/markdownLink.js'; +import { MarkdownToken } from '../codecs/base/markdownCodec/tokens/markdownToken.js'; +import { isPromptOrInstructionsFile } from '../config/promptFileLocations.js'; +import { FrontMatterHeader } from '../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; import { OpenFailed, NotPromptFile, RecursiveReference, FolderReference, ResolveError } from '../../promptFileReferenceErrors.js'; import { type IPromptContentsProviderOptions, DEFAULT_OPTIONS as CONTENTS_PROVIDER_DEFAULT_OPTIONS } from '../contentProviders/promptContentsProviderBase.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; /** * Options of the {@link BasePromptParser} class. @@ -511,7 +512,7 @@ export class BasePromptParser /** * Start the prompt parser. */ - public start(): this { + public start(token?: CancellationToken): this { // if already started, nothing to do if (this.started) { return this; @@ -525,7 +526,7 @@ export class BasePromptParser return this; } - this.promptContentsProvider.start(); + this.promptContentsProvider.start(token); return this; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts index 7cb97d12a14..cea4c15d430 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts @@ -11,12 +11,12 @@ import { PromptDescriptionMetadata } from './metadata/index.js'; import { type TInstructionsMetadata } from './instructionsHeader.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; import { Disposable } from '../../../../../../../base/common/lifecycle.js'; -import { ObjectStream } from '../../../../../../../editor/common/codecs/utils/objectStream.js'; +import { ObjectStream } from '../../codecs/base/utils/objectStream.js'; import { PromptMetadataError, PromptMetadataWarning, type TDiagnostic } from './diagnostics.js'; -import { SimpleToken } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/index.js'; -import { FrontMatterRecord } from '../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; -import { FrontMatterHeader } from '../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js'; -import { FrontMatterDecoder, type TFrontMatterToken } from '../../../../../../../editor/common/codecs/frontMatterCodec/frontMatterDecoder.js'; +import { SimpleToken } from '../../codecs/base/simpleCodec/tokens/tokens.js'; +import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; +import { FrontMatterHeader } from '../../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; +import { FrontMatterDecoder, type TFrontMatterToken } from '../../codecs/base/frontMatterCodec/frontMatterDecoder.js'; /** * A metadata utility class "dehydrated" into a plain data object with diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts index a024a6bf11a..1f13901f960 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts @@ -5,8 +5,8 @@ import { PromptApplyToMetadata } from './metadata/applyTo.js'; import { HeaderBase, IHeaderMetadata, type TDehydrated } from './headerBase.js'; -import { PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; -import { FrontMatterRecord } from '../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { PromptsType } from '../../promptTypes.js'; +import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Metadata utility object for instruction files. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts index b97dd2f0f63..b4dfa24bdc0 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts @@ -5,10 +5,10 @@ import { PromptStringMetadata } from './base/string.js'; import { localize } from '../../../../../../../../nls.js'; -import { INSTRUCTIONS_LANGUAGE_ID } from '../../../constants.js'; +import { INSTRUCTIONS_LANGUAGE_ID } from '../../../promptTypes.js'; import { isEmptyPattern, parse, splitGlobAware } from '../../../../../../../../base/common/glob.js'; import { PromptMetadataDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../diagnostics.js'; -import { FrontMatterRecord, FrontMatterToken } from '../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Name of the metadata record in the prompt header. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts index 8a24c72dcbc..2b572e88ae7 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts @@ -8,8 +8,8 @@ import { localize } from '../../../../../../../../../nls.js'; import { assert } from '../../../../../../../../../base/common/assert.js'; import { isOneOf } from '../../../../../../../../../base/common/types.js'; import { PromptMetadataDiagnostic, PromptMetadataError } from '../../diagnostics.js'; -import { FrontMatterSequence } from '../../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; -import { FrontMatterRecord, FrontMatterString } from '../../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterSequence } from '../../../../codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; +import { FrontMatterRecord, FrontMatterString } from '../../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Enum type is the special case of the {@link PromptStringMetadata string} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts index a158d7d43a2..aa7a4660f66 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts @@ -6,7 +6,7 @@ import { assert } from '../../../../../../../../../base/common/assert.js'; import { Range } from '../../../../../../../../../editor/common/core/range.js'; import { PromptMetadataDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../../diagnostics.js'; -import { FrontMatterRecord } from '../../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterRecord } from '../../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Supported primitive types for metadata values in a prompt header. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts index 6588310c4e1..d240efa069a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts @@ -6,8 +6,8 @@ import { PromptMetadataRecord } from './record.js'; import { localize } from '../../../../../../../../../nls.js'; import { PromptMetadataDiagnostic, PromptMetadataError } from '../../diagnostics.js'; -import { FrontMatterSequence } from '../../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; -import { FrontMatterRecord, FrontMatterString } from '../../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterSequence } from '../../../../codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; +import { FrontMatterRecord, FrontMatterString } from '../../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Base class for all metadata records with a `string` value. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts index 619352d2731..aaa8fa3a640 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { PromptStringMetadata } from './base/string.js'; -import { FrontMatterRecord, FrontMatterToken } from '../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Name of the metadata record in the prompt header. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts index 865f301b651..6137e90ffae 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts @@ -5,7 +5,7 @@ import { ChatMode } from '../../../../constants.js'; import { PromptEnumMetadata } from './base/enum.js'; -import { FrontMatterRecord, FrontMatterToken } from '../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Name of the metadata record in the prompt header. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts index e2b73e80ef4..c98638186f2 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts @@ -6,8 +6,8 @@ import { PromptMetadataRecord } from './base/record.js'; import { localize } from '../../../../../../../../nls.js'; import { PromptMetadataDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../diagnostics.js'; -import { FrontMatterSequence } from '../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; -import { FrontMatterArray, FrontMatterRecord, FrontMatterString, FrontMatterToken, FrontMatterValueToken } from '../../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { FrontMatterSequence } from '../../../codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; +import { FrontMatterArray, FrontMatterRecord, FrontMatterString, FrontMatterToken, FrontMatterValueToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Name of the metadata record in the prompt header. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts index 6f87a5f1331..822d78f1b6c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts @@ -5,7 +5,7 @@ import { type TDehydrated } from './headerBase.js'; import { PromptHeader, type IPromptMetadata } from './promptHeader.js'; -import { PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; +import { PromptsType } from '../../promptTypes.js'; /** * Metadata utility object for mode files. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts index 76049d743f4..a6ed0357158 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts @@ -10,8 +10,8 @@ import { assert } from '../../../../../../../base/common/assert.js'; import { assertDefined } from '../../../../../../../base/common/types.js'; import { PromptToolsMetadata, PromptModeMetadata } from './metadata/index.js'; import { HeaderBase, IHeaderMetadata, type TDehydrated } from './headerBase.js'; -import { PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; -import { FrontMatterRecord } from '../../../../../../../editor/common/codecs/frontMatterCodec/tokens/index.js'; +import { PromptsType } from '../../promptTypes.js'; +import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; /** * Metadata utility object for prompt files. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts index c9f4407f01d..3e7aad075df 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts @@ -9,23 +9,23 @@ import { IPromptContentsProvider } from '../contentProviders/types.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; import { BasePromptParser, IPromptParserOptions } from './basePromptParser.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { isUntitled } from '../../../../../../platform/prompts/common/prompts.js'; import { TextModelContentsProvider } from '../contentProviders/textModelContentsProvider.js'; import { FilePromptContentProvider } from '../contentProviders/filePromptContentsProvider.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { Schemas } from '../../../../../../base/common/network.js'; /** * Get prompt contents provider object based on the prompt type. */ -const getContentsProvider = ( +function getContentsProvider( uri: URI, options: Partial, modelService: IModelService, - instaService: IInstantiationService, -): IPromptContentsProvider => { + instaService: IInstantiationService +): IPromptContentsProvider { // use text model contents provider for `untitled` documents - if (isUntitled(uri)) { + if (uri.scheme === Schemas.untitled) { const model = modelService.getModel(uri); assertDefined( @@ -39,7 +39,7 @@ const getContentsProvider = ( return instaService .createInstance(FilePromptContentProvider, uri, options); -}; +} /** * General prompt parser class that automatically infers a prompt diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts new file mode 100644 index 00000000000..9e277049e46 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ConfigMigration } from './config/configMigration.js'; +import { Registry } from '../../../../../platform/registry/common/platform.js'; +import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js'; +import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } from '../../../../common/contributions.js'; +import { PromptLinkProvider } from './languageProviders/promptLinkProvider.js'; +import { PromptLinkDiagnosticsInstanceManager } from './languageProviders/promptLinkDiagnosticsProvider.js'; +import { PromptHeaderDiagnosticsInstanceManager } from './languageProviders/promptHeaderDiagnosticsProvider.js'; +import { isWindows } from '../../../../../base/common/platform.js'; +import { PromptPathAutocompletion } from './languageProviders/promptPathAutocompletion.js'; + + +/** + * Function that registers all prompt-file related contributions. + */ +export function registerPromptFileContributions(): void { + + // all language constributions + + registerContribution(PromptLinkProvider); + registerContribution(PromptLinkDiagnosticsInstanceManager); + registerContribution(PromptHeaderDiagnosticsInstanceManager); + /** + * PromptDecorationsProviderInstanceManager is currently disabled because the only currently + * available decoration is the Front Matter header, which we decided to disable for now. + * Add it back when more decorations are needed. + */ + // registerContribution(PromptDecorationsProviderInstanceManager); , + + + /** + * We restrict this provider to `Unix` machines for now because of + * the filesystem paths differences on `Windows` operating system. + * + * Notes on `Windows` support: + * - we add the `./` for the first path component, which may not work on `Windows` + * - the first path component of the absolute paths must be a drive letter + */ + if (!isWindows) { + registerContribution(PromptPathAutocompletion); + } + + registerContribution(ConfigMigration); +} + +/** + * Type for a generic workbench contribution. + */ +export type TContribution = new (...args: any[]) => IWorkbenchContribution; + +/** + * Register a specific workbench contribution. + */ +function registerContribution(contribution: TContribution): void { + Registry.as(Extensions.Workbench).registerWorkbenchContribution(contribution, LifecyclePhase.Eventually); +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/constants.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts similarity index 73% rename from src/vs/workbench/contrib/chat/common/promptSyntax/constants.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts index 5a5c5e8cbc6..a1f2f9c8569 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/constants.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageSelector } from '../../../../../editor/common/languageSelector.js'; -import { PromptsType } from '../../../../../platform/prompts/common/prompts.js'; /** * Documentation link for the reusable prompts feature. @@ -48,3 +47,30 @@ export function getLanguageIdForPromptsType(type: PromptsType): string { throw new Error(`Unknown prompt type: ${type}`); } } + +export function getPromptsTypeForLanguageId(languageId: string): PromptsType | undefined { + switch (languageId) { + case PROMPT_LANGUAGE_ID: + return PromptsType.prompt; + case INSTRUCTIONS_LANGUAGE_ID: + return PromptsType.instructions; + case MODE_LANGUAGE_ID: + return PromptsType.mode; + default: + return undefined; + } +} + + +/** + * What the prompt is used for. + */ +export enum PromptsType { + instructions = 'instructions', + prompt = 'prompt', + mode = 'mode' +} +export function isValidPromptType(type: string): type is PromptsType { + return Object.values(PromptsType).includes(type as PromptsType); +} + diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 75baed4e1eb..db6bc946c57 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -3,392 +3,231 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { flatten } from '../utils/treeUtils.js'; -import { localize } from '../../../../../../nls.js'; -import { PROMPT_LANGUAGE_ID } from '../constants.js'; -import { PromptParser } from '../parsers/promptParser.js'; -import { match, splitGlobAware } from '../../../../../../base/common/glob.js'; -import { pick } from '../../../../../../base/common/arrays.js'; -import { type URI } from '../../../../../../base/common/uri.js'; -import { type IPromptFileReference } from '../parsers/types.js'; -import { assert } from '../../../../../../base/common/assert.js'; -import { basename } from '../../../../../../base/common/path.js'; -import { ResourceSet } from '../../../../../../base/common/map.js'; -import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; -import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { TTree } from '../utils/treeUtils.js'; +import { ChatMode } from '../../constants.js'; +import { URI } from '../../../../../../base/common/uri.js'; import { Event } from '../../../../../../base/common/event.js'; -import { type ITextModel } from '../../../../../../editor/common/model.js'; -import { ObjectCache } from '../../../../../../base/common/objectCache.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; +import { TMetadata } from '../parsers/promptHeader/headerBase.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { IDisposable } from '../../../../../../base/common/lifecycle.js'; import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; -import { ILabelService } from '../../../../../../platform/label/common/label.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { logTime, TLogFunction } from '../../../../../../base/common/decorators/logTime.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; -import type { IChatPromptSlashCommand, ICustomChatMode, IMetadata, IPromptPath, IPromptsService, TPromptsStorage } from './types.js'; -import { getCleanPromptName, isValidPromptType, PROMPT_FILE_EXTENSION, PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; +import { PromptsType } from '../promptTypes.js'; +import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ITopError } from '../parsers/types.js'; /** * Provides prompt services. */ -export class PromptsService extends Disposable implements IPromptsService { - public declare readonly _serviceBrand: undefined; +export const IPromptsService = createDecorator('IPromptsService'); + +/** + * Where the prompt is stored. + */ +export type TPromptsStorage = 'local' | 'user'; + +/** + * Represents a prompt path with its type. + * This is used for both prompt files and prompt source folders. + */ +export interface IPromptPath { + /** + * URI of the prompt. + */ + readonly uri: URI; /** - * Cache of text model content prompt parsers. + * Storage of the prompt. */ - private readonly cache: ObjectCache; + readonly storage: TPromptsStorage; /** - * Prompt files locator utility. + * Type of the prompt (e.g. 'prompt' or 'instructions'). */ - private readonly fileLocator: PromptFilesLocator; - - /** - * Function used by the `@logTime` decorator to log - * execution time of some of the decorated methods. - */ - public logTime: TLogFunction; - - /** - * Lazily created event that is fired when the custom chat modes change. - */ - private onDidChangeCustomChatModesEvent: Event | undefined; - - constructor( - @ILogService public readonly logger: ILogService, - @ILabelService private readonly labelService: ILabelService, - @IModelService private readonly modelService: IModelService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUserDataProfileService private readonly userDataService: IUserDataProfileService, - ) { - super(); - - this.fileLocator = this._register(this.instantiationService.createInstance(PromptFilesLocator)); - - this.logTime = this.logger.trace.bind(this.logger); - - // the factory function below creates a new prompt parser object - // for the provided model, if no active non-disposed parser exists - this.cache = this._register( - new ObjectCache((model) => { - assert( - model.isDisposed() === false, - 'Text model must not be disposed.', - ); - - /** - * Note! When/if shared with "file" prompts, the `seenReferences` array below must be taken into account. - * Otherwise consumers will either see incorrect failing or incorrect successful results, based on their - * use case, timing of their calls to the {@link getSyntaxParserFor} function, and state of this service. - */ - const parser: TextModelPromptParser = instantiationService.createInstance( - TextModelPromptParser, - model, - { seenReferences: [] }, - ).start(); - - // this is a sanity check and the contract of the object cache, - // we must return a non-disposed object from this factory function - parser.assertNotDisposed( - 'Created prompt parser must not be disposed.', - ); - - return parser; - }) - ); - } - - /** - * Emitter for the custom chat modes change event. - */ - public get onDidChangeCustomChatModes(): Event { - if (!this.onDidChangeCustomChatModesEvent) { - this.onDidChangeCustomChatModesEvent = this.fileLocator.getFilesUpdatedEvent(PromptsType.mode); - } - return this.onDidChangeCustomChatModesEvent; - } - - - /** - * @throws {Error} if: - * - the provided model is disposed - * - newly created parser is disposed immediately on initialization. - * See factory function in the {@link constructor} for more info. - */ - public getSyntaxParserFor( - model: ITextModel, - ): TextModelPromptParser & { isDisposed: false } { - assert( - model.isDisposed() === false, - 'Cannot create a prompt syntax parser for a disposed model.', - ); - - return this.cache.get(model); - } - - public async listPromptFiles(type: PromptsType, token: CancellationToken): Promise { - const prompts = await Promise.all([ - this.fileLocator.listFiles(type, 'user', token) - .then(withType('user', type)), - this.fileLocator.listFiles(type, 'local', token) - .then(withType('local', type)), - ]); - - return prompts.flat(); - } - - public getSourceFolders(type: PromptsType): readonly IPromptPath[] { - // sanity check to make sure we don't miss a new - // prompt type that could be added in the future - assert(isValidPromptType(type), `Unknown prompt type '${type}'.`); - - const result: IPromptPath[] = []; - - for (const uri of this.fileLocator.getConfigBasedSourceFolders(type)) { - result.push({ uri, storage: 'local', type }); - } - const userHome = this.userDataService.currentProfile.promptsHome; - result.push({ uri: userHome, storage: 'user', type }); - - return result; - } - - public asPromptSlashCommand(command: string): IChatPromptSlashCommand | undefined { - if (command.match(/^[\w_\-\.]+$/)) { - return { command, detail: localize('prompt.file.detail', 'Prompt file: {0}', command) }; - } - return undefined; - } - - public async resolvePromptSlashCommand(data: IChatPromptSlashCommand): Promise { - const promptUri = await this.getPromptPath(data); - if (!promptUri) { - return undefined; - } - return await this.getMetadata(promptUri); - } - - private async getPromptPath(data: IChatPromptSlashCommand): Promise { - if (data.promptPath) { - return data.promptPath.uri; - } - - const files = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); - const command = data.command; - const result = files.find(file => getPromptCommandName(file.uri.path) === command); - if (result) { - return result.uri; - } - const textModel = this.modelService.getModels().find(model => model.getLanguageId() === PROMPT_LANGUAGE_ID && getPromptCommandName(model.uri.path) === command); - if (textModel) { - return textModel.uri; - } - return undefined; - } - - public async findPromptSlashCommands(): Promise { - const promptFiles = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); - return promptFiles.map(promptPath => { - const command = getPromptCommandName(promptPath.uri.path); - return { - command, - detail: localize('prompt.file.detail', 'Prompt file: {0}', this.labelService.getUriLabel(promptPath.uri, { relative: true })), - promptPath - }; - }); - } - - @logTime() - public async getCustomChatModes(): Promise { - const modeFiles = (await this.listPromptFiles(PromptsType.mode, CancellationToken.None)) - .map(modeFile => modeFile.uri); - - const metadataList = await Promise.all( - modeFiles.map(async (uri): Promise => { - let parser: PromptParser | undefined; - try { - // Note! this can be (and should be) improved by using shared parser instances - // that the `getSyntaxParserFor` method provides for opened documents. - parser = this.instantiationService.createInstance( - PromptParser, - uri, - { allowNonPromptFiles: true }, - ).start(); - - await parser.settled(); - - const { metadata } = parser; - const tools = (metadata && ('tools' in metadata)) - ? metadata.tools - : undefined; - - const body = await parser.getBody(); - return { - uri: uri, - name: getCleanPromptName(uri), - description: metadata?.description, - tools, - body, - }; - } finally { - parser?.dispose(); - } - }), - ); - - return metadataList; - } - - @logTime() - public async findInstructionFilesFor( - files: readonly URI[], - ): Promise { - const instructionFiles = await this.listPromptFiles(PromptsType.instructions, CancellationToken.None); - if (instructionFiles.length === 0) { - return []; - } - - const instructions = await this.getAllMetadata( - instructionFiles.map(pick('uri')), - ); - - const foundFiles = new ResourceSet(); - for (const instruction of instructions.flatMap(flatten)) { - const { metadata, uri } = instruction; - - if (metadata?.promptType !== PromptsType.instructions) { - continue; - } - - const { applyTo } = metadata; - if (applyTo === undefined) { - continue; - } - - const patterns = splitGlobAware(applyTo, ','); - const patterMatches = (pattern: string) => { - pattern = pattern.trim(); - if (pattern.length === 0) { - // if glob pattern is empty, skip it - return false; - } - if (pattern === '**' || pattern === '**/*' || pattern === '*') { - // if glob pattern is one of the special wildcard values, - // add the instructions file event if no files are attached - return true; - } - if (!pattern.startsWith('/') && !pattern.startsWith('**/')) { - // support relative glob patterns, e.g. `src/**/*.js` - pattern = '**/' + pattern; - } - - // match each attached file with each glob pattern and - // add the instructions file if its rule matches the file - for (const file of files) { - // if the file is not a valid URI, skip it - if (match(pattern, file.path)) { - return true; - } - } - return false; - }; - - if (patterns.some(patterMatches)) { - foundFiles.add(uri); - } - } - return [...foundFiles]; - } - - public async getMetadata(promptFileUri: URI): Promise { - const metaDatas = await this.getAllMetadata([promptFileUri]); - return metaDatas[0]; - } - - @logTime() - public async getAllMetadata( - promptUris: readonly URI[], - ): Promise { - const metadata = await Promise.all( - promptUris.map(async (uri) => { - let parser: PromptParser | undefined; - try { - parser = this.instantiationService.createInstance( - PromptParser, - uri, - { allowNonPromptFiles: true }, - ).start(); - - await parser.allSettled(); - - return collectMetadata(parser); - } finally { - parser?.dispose(); - } - }), - ); - - return metadata; - } + readonly type: PromptsType; } /** - * Collect all metadata from prompt file references - * into a single hierarchical tree structure. + * Type for a shared prompt parser instance returned by the {@link IPromptsService}. + * Because the parser is shared, we omit the `dispose` method from + * the original type so the caller cannot dispose it prematurely */ -const collectMetadata = ( - reference: Pick, -): IMetadata => { - const childMetadata = []; - for (const child of reference.references) { - if (child.errorCondition !== undefined) { - continue; - } +export type TSharedPrompt = Omit; - childMetadata.push(collectMetadata(child)); - } +/** + * Metadata node object in a hierarchical tree of prompt references. + */ +export interface IMetadata { + /** + * URI of a prompt file. + */ + readonly uri: URI; - const children = (childMetadata.length > 0) - ? childMetadata - : undefined; + /** + * Metadata of the prompt file. + */ + readonly metadata: TMetadata | null; - return { - uri: reference.uri, - metadata: reference.metadata, - children, - }; -}; + /** + * List of metadata for each valid child prompt reference. + */ + readonly children?: readonly TTree[]; +} -export function getPromptCommandName(path: string): string { - const name = basename(path, PROMPT_FILE_EXTENSION); - return name; +export interface ICustomChatMode { + /** + * URI of a custom chat mode file. + */ + readonly uri: URI; + + /** + * Name of the custom chat mode. + */ + readonly name: string; + + /** + * Description of the mode + */ + readonly description?: string; + + /** + * Tools metadata in the prompt header. + */ + readonly tools?: readonly string[]; + + /** + * Contents of the custom chat mode file body. + */ + readonly body: string; } /** - * Utility to add a provided prompt `storage` and - * `type` attributes to a prompt URI. + * Type of combined tools metadata for the case + * when the prompt is in the agent mode. */ -const addType = ( - storage: TPromptsStorage, - type: PromptsType, -): (uri: URI) => IPromptPath => { - return (uri) => { - return { uri, storage, type }; - }; -}; +interface ICombinedAgentToolsMetadata { + /** + * List of combined tools metadata for + * the entire tree of prompt references. + */ + readonly tools: readonly string[] | undefined; + + /** + * Resulting chat mode of a prompt, based on modes + * used in the entire tree of prompt references. + */ + readonly mode: ChatMode.Agent; +} /** - * Utility to add a provided prompt `type` to a list of prompt URIs. + * Type of combined tools metadata for the case + * when the prompt is in non-agent mode. */ -const withType = ( - storage: TPromptsStorage, - type: PromptsType, -): (uris: readonly URI[]) => (readonly IPromptPath[]) => { - return (uris) => { - return uris - .map(addType(storage, type)); - }; -}; +interface ICombinedNonAgentToolsMetadata { + /** + * List of combined tools metadata is empty + * when the prompt is in non-agent mode. + */ + readonly tools: undefined; + + /** + * Resulting chat mode of a prompt, based on modes + * used in the entire tree of prompt references. + */ + readonly mode?: ChatMode.Ask | ChatMode.Edit; +} + +/** + * General type of the combined tools metadata. + */ +export type TCombinedToolsMetadata = ICombinedAgentToolsMetadata | ICombinedNonAgentToolsMetadata; + +/** + * Provides prompt services. + */ +export interface IPromptsService extends IDisposable { + readonly _serviceBrand: undefined; + + /** + * Get a prompt syntax parser for the provided text model. + * See {@link TextModelPromptParser} for more info on the parser API. + */ + getSyntaxParserFor(model: ITextModel): TSharedPrompt & { isDisposed: false }; + + /** + * List all available prompt files. + */ + listPromptFiles(type: PromptsType, token: CancellationToken): Promise; + + /** + * Get a list of prompt source folders based on the provided prompt type. + */ + getSourceFolders(type: PromptsType): readonly IPromptPath[]; + + /** + * Returns a prompt command if the command name. + * Undefined is returned if the name does not look like a file name of a prompt file. + */ + asPromptSlashCommand(name: string): IChatPromptSlashCommand | undefined; + + /** + * Gets the prompt file for a slash command. + */ + resolvePromptSlashCommand(data: IChatPromptSlashCommand): Promise; + + /** + * Returns a prompt command if the command name is valid. + */ + findPromptSlashCommands(): Promise; + + /** + * Find all instruction files which have a glob pattern in their + * 'applyTo' metadata record that match the provided list of files. + */ + findInstructionFilesFor(fileUris: readonly URI[]): Promise; + + /** + * Event that is triggered when the list of custom chat modes changes. + */ + readonly onDidChangeCustomChatModes: Event; + + /** + * Finds all available custom chat modes + */ + getCustomChatModes(token: CancellationToken): Promise; + + /** + * Gets the metadata for the given prompt file uri. + */ + getMetadata(promptFileUri: URI): Promise; + + /** + * Get all metadata for entire prompt references tree + * that spans out of each of the provided files. + * + * In other words, the metadata tree is built starting from + * each of the provided files, therefore the result is a number + * of metadata trees, one for each file. + */ + getAllMetadata(promptUris: readonly URI[]): Promise; + + /** + * Parses the provided URI + * @param uris + */ + parse(uri: URI, token: CancellationToken): Promise; +} + +export interface IChatPromptSlashCommand { + readonly command: string; + readonly detail: string; + readonly promptPath?: IPromptPath; +} + + +export interface IPromptParserResult { + readonly uri: URI; + readonly metadata: TMetadata | null; + readonly topError: ITopError | undefined; + readonly allValidReferences: readonly URI[]; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts new file mode 100644 index 00000000000..31f3b1d8293 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -0,0 +1,386 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { flatten } from '../utils/treeUtils.js'; +import { localize } from '../../../../../../nls.js'; +import { isValidPromptType, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { PromptParser } from '../parsers/promptParser.js'; +import { match, splitGlobAware } from '../../../../../../base/common/glob.js'; +import { type URI } from '../../../../../../base/common/uri.js'; +import { type IPromptFileReference } from '../parsers/types.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { basename } from '../../../../../../base/common/path.js'; +import { ResourceSet } from '../../../../../../base/common/map.js'; +import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { Event } from '../../../../../../base/common/event.js'; +import { type ITextModel } from '../../../../../../editor/common/model.js'; +import { ObjectCache } from '../utils/objectCache.js'; +import { ILogService } from '../../../../../../platform/log/common/log.js'; +import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; +import { ILabelService } from '../../../../../../platform/label/common/label.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; +import type { IChatPromptSlashCommand, ICustomChatMode, IMetadata, IPromptParserResult, IPromptPath, IPromptsService, TPromptsStorage } from './promptsService.js'; +import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; + +/** + * Provides prompt services. + */ +export class PromptsService extends Disposable implements IPromptsService { + public declare readonly _serviceBrand: undefined; + + /** + * Cache of text model content prompt parsers. + */ + private readonly cache: ObjectCache; + + /** + * Prompt files locator utility. + */ + private readonly fileLocator: PromptFilesLocator; + + + /** + * Lazily created event that is fired when the custom chat modes change. + */ + private onDidChangeCustomChatModesEvent: Event | undefined; + + constructor( + @ILogService public readonly logger: ILogService, + @ILabelService private readonly labelService: ILabelService, + @IModelService private readonly modelService: IModelService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUserDataProfileService private readonly userDataService: IUserDataProfileService, + ) { + super(); + + this.fileLocator = this._register(this.instantiationService.createInstance(PromptFilesLocator)); + + // the factory function below creates a new prompt parser object + // for the provided model, if no active non-disposed parser exists + this.cache = this._register( + new ObjectCache((model) => { + assert( + model.isDisposed() === false, + 'Text model must not be disposed.', + ); + + /** + * Note! When/if shared with "file" prompts, the `seenReferences` array below must be taken into account. + * Otherwise consumers will either see incorrect failing or incorrect successful results, based on their + * use case, timing of their calls to the {@link getSyntaxParserFor} function, and state of this service. + */ + const parser: TextModelPromptParser = instantiationService.createInstance( + TextModelPromptParser, + model, + { seenReferences: [] }, + ).start(); + + // this is a sanity check and the contract of the object cache, + // we must return a non-disposed object from this factory function + parser.assertNotDisposed( + 'Created prompt parser must not be disposed.', + ); + + return parser; + }) + ); + } + + /** + * Emitter for the custom chat modes change event. + */ + public get onDidChangeCustomChatModes(): Event { + if (!this.onDidChangeCustomChatModesEvent) { + this.onDidChangeCustomChatModesEvent = this._register(this.fileLocator.createFilesUpdatedEvent(PromptsType.mode)).event; + } + return this.onDidChangeCustomChatModesEvent; + } + + + /** + * @throws {Error} if: + * - the provided model is disposed + * - newly created parser is disposed immediately on initialization. + * See factory function in the {@link constructor} for more info. + */ + public getSyntaxParserFor(model: ITextModel): TextModelPromptParser & { isDisposed: false } { + assert( + model.isDisposed() === false, + 'Cannot create a prompt syntax parser for a disposed model.', + ); + + return this.cache.get(model); + } + + public async listPromptFiles(type: PromptsType, token: CancellationToken): Promise { + const prompts = await Promise.all([ + this.fileLocator.listFiles(type, 'user', token) + .then(withType('user', type)), + this.fileLocator.listFiles(type, 'local', token) + .then(withType('local', type)), + ]); + + return prompts.flat(); + } + + public getSourceFolders(type: PromptsType): readonly IPromptPath[] { + // sanity check to make sure we don't miss a new + // prompt type that could be added in the future + assert(isValidPromptType(type), `Unknown prompt type '${type}'.`); + + const result: IPromptPath[] = []; + + for (const uri of this.fileLocator.getConfigBasedSourceFolders(type)) { + result.push({ uri, storage: 'local', type }); + } + const userHome = this.userDataService.currentProfile.promptsHome; + result.push({ uri: userHome, storage: 'user', type }); + + return result; + } + + public asPromptSlashCommand(command: string): IChatPromptSlashCommand | undefined { + if (command.match(/^[\w_\-\.]+$/)) { + return { command, detail: localize('prompt.file.detail', 'Prompt file: {0}', command) }; + } + return undefined; + } + + public async resolvePromptSlashCommand(data: IChatPromptSlashCommand): Promise { + const promptUri = await this.getPromptPath(data); + if (!promptUri) { + return undefined; + } + return await this.getMetadata(promptUri); + } + + private async getPromptPath(data: IChatPromptSlashCommand): Promise { + if (data.promptPath) { + return data.promptPath.uri; + } + + const files = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); + const command = data.command; + const result = files.find(file => getPromptCommandName(file.uri.path) === command); + if (result) { + return result.uri; + } + const textModel = this.modelService.getModels().find(model => model.getLanguageId() === PROMPT_LANGUAGE_ID && getPromptCommandName(model.uri.path) === command); + if (textModel) { + return textModel.uri; + } + return undefined; + } + + public async findPromptSlashCommands(): Promise { + const promptFiles = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); + return promptFiles.map(promptPath => { + const command = getPromptCommandName(promptPath.uri.path); + return { + command, + detail: localize('prompt.file.detail', 'Prompt file: {0}', this.labelService.getUriLabel(promptPath.uri, { relative: true })), + promptPath + }; + }); + } + + public async getCustomChatModes(token: CancellationToken): Promise { + const modeFiles = (await this.listPromptFiles(PromptsType.mode, token)) + .map(modeFile => modeFile.uri); + + const metadataList = await Promise.all( + modeFiles.map(async (uri): Promise => { + let parser: PromptParser | undefined; + try { + // Note! this can be (and should be) improved by using shared parser instances + // that the `getSyntaxParserFor` method provides for opened documents. + parser = this.instantiationService.createInstance( + PromptParser, + uri, + { allowNonPromptFiles: true }, + ).start(token); + + await parser.settled(); + + const { metadata } = parser; + const tools = (metadata && ('tools' in metadata)) + ? metadata.tools + : undefined; + + const body = await parser.getBody(); + return { + uri: uri, + name: getCleanPromptName(uri), + description: metadata?.description, + tools, + body, + }; + } finally { + parser?.dispose(); + } + }), + ); + + return metadataList; + } + + public async parse(uri: URI, token: CancellationToken): Promise { + let parser: PromptParser | undefined; + try { + parser = this.instantiationService.createInstance(PromptParser, uri, { allowNonPromptFiles: true }).start(token); + await parser.settled(); + // make a copy, to avoid leaking the parser instance + return { + uri: parser.uri, + metadata: parser.metadata, + topError: parser.topError, + allValidReferences: parser.allValidReferences.map(ref => ref.uri) + }; + } finally { + parser?.dispose(); + } + } + + + public async findInstructionFilesFor(files: readonly URI[]): Promise { + const instructionFiles = await this.listPromptFiles(PromptsType.instructions, CancellationToken.None); + if (instructionFiles.length === 0) { + return []; + } + + const instructions = await this.getAllMetadata( + instructionFiles.map(file => file.uri), + ); + + const foundFiles = new ResourceSet(); + for (const instruction of instructions.flatMap(flatten)) { + const { metadata, uri } = instruction; + + if (metadata?.promptType !== PromptsType.instructions) { + continue; + } + + const { applyTo } = metadata; + if (applyTo === undefined) { + continue; + } + + const patterns = splitGlobAware(applyTo, ','); + const patterMatches = (pattern: string) => { + pattern = pattern.trim(); + if (pattern.length === 0) { + // if glob pattern is empty, skip it + return false; + } + if (pattern === '**' || pattern === '**/*' || pattern === '*') { + // if glob pattern is one of the special wildcard values, + // add the instructions file event if no files are attached + return true; + } + if (!pattern.startsWith('/') && !pattern.startsWith('**/')) { + // support relative glob patterns, e.g. `src/**/*.js` + pattern = '**/' + pattern; + } + + // match each attached file with each glob pattern and + // add the instructions file if its rule matches the file + for (const file of files) { + // if the file is not a valid URI, skip it + if (match(pattern, file.path)) { + return true; + } + } + return false; + }; + + if (patterns.some(patterMatches)) { + foundFiles.add(uri); + } + } + return [...foundFiles]; + } + + public async getMetadata(promptFileUri: URI): Promise { + const metaDatas = await this.getAllMetadata([promptFileUri]); + return metaDatas[0]; + } + + public async getAllMetadata(promptUris: readonly URI[]): Promise { + const metadata = await Promise.all( + promptUris.map(async (uri) => { + let parser: PromptParser | undefined; + try { + parser = this.instantiationService.createInstance( + PromptParser, + uri, + { allowNonPromptFiles: true }, + ).start(); + + await parser.allSettled(); + + return collectMetadata(parser); + } finally { + parser?.dispose(); + } + }), + ); + + return metadata; + } +} + +/** + * Collect all metadata from prompt file references + * into a single hierarchical tree structure. + */ +function collectMetadata(reference: Pick): IMetadata { + const childMetadata = []; + for (const child of reference.references) { + if (child.errorCondition !== undefined) { + continue; + } + + childMetadata.push(collectMetadata(child)); + } + + const children = (childMetadata.length > 0) + ? childMetadata + : undefined; + + return { + uri: reference.uri, + metadata: reference.metadata, + children, + }; +} + +export function getPromptCommandName(path: string): string { + const name = basename(path, PROMPT_FILE_EXTENSION); + return name; +} + +/** + * Utility to add a provided prompt `storage` and + * `type` attributes to a prompt URI. + */ +function addType(storage: TPromptsStorage, type: PromptsType): (uri: URI) => IPromptPath { + return (uri) => { + return { uri, storage, type }; + }; +} + +/** + * Utility to add a provided prompt `type` to a list of prompt URIs. + */ +function withType(storage: TPromptsStorage, type: PromptsType): (uris: readonly URI[]) => (readonly IPromptPath[]) { + return (uris) => { + return uris + .map(addType(storage, type)); + }; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts deleted file mode 100644 index ce41e74a705..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts +++ /dev/null @@ -1,224 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TTree } from '../utils/treeUtils.js'; -import { ChatMode } from '../../constants.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { Event } from '../../../../../../base/common/event.js'; -import { TMetadata } from '../parsers/promptHeader/headerBase.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; -import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; - -/** - * Provides prompt services. - */ -export const IPromptsService = createDecorator('IPromptsService'); - -/** - * Where the prompt is stored. - */ -export type TPromptsStorage = 'local' | 'user'; - -/** - * Represents a prompt path with its type. - * This is used for both prompt files and prompt source folders. - */ -export interface IPromptPath { - /** - * URI of the prompt. - */ - readonly uri: URI; - - /** - * Storage of the prompt. - */ - readonly storage: TPromptsStorage; - - /** - * Type of the prompt (e.g. 'prompt' or 'instructions'). - */ - readonly type: PromptsType; -} - -/** - * Type for a shared prompt parser instance returned by the {@link IPromptsService}. - * Because the parser is shared, we omit the `dispose` method from - * the original type so the caller cannot dispose it prematurely - */ -export type TSharedPrompt = Omit; - -/** - * Metadata node object in a hierarchical tree of prompt references. - */ -export interface IMetadata { - /** - * URI of a prompt file. - */ - readonly uri: URI; - - /** - * Metadata of the prompt file. - */ - readonly metadata: TMetadata | null; - - /** - * List of metadata for each valid child prompt reference. - */ - readonly children?: readonly TTree[]; -} - -export interface ICustomChatMode { - /** - * URI of a custom chat mode file. - */ - readonly uri: URI; - - /** - * Name of the custom chat mode. - */ - readonly name: string; - - /** - * Description of the mode - */ - readonly description?: string; - - /** - * Tools metadata in the prompt header. - */ - readonly tools?: readonly string[]; - - /** - * Contents of the custom chat mode file body. - */ - readonly body: string; -} - -/** - * Type of combined tools metadata for the case - * when the prompt is in the agent mode. - */ -interface ICombinedAgentToolsMetadata { - /** - * List of combined tools metadata for - * the entire tree of prompt references. - */ - readonly tools: readonly string[] | undefined; - - /** - * Resulting chat mode of a prompt, based on modes - * used in the entire tree of prompt references. - */ - readonly mode: ChatMode.Agent; -} - -/** - * Type of combined tools metadata for the case - * when the prompt is in non-agent mode. - */ -interface ICombinedNonAgentToolsMetadata { - /** - * List of combined tools metadata is empty - * when the prompt is in non-agent mode. - */ - readonly tools: undefined; - - /** - * Resulting chat mode of a prompt, based on modes - * used in the entire tree of prompt references. - */ - readonly mode?: ChatMode.Ask | ChatMode.Edit; -} - -/** - * General type of the combined tools metadata. - */ -export type TCombinedToolsMetadata = ICombinedAgentToolsMetadata | ICombinedNonAgentToolsMetadata; - -/** - * Provides prompt services. - */ -export interface IPromptsService extends IDisposable { - readonly _serviceBrand: undefined; - - /** - * Get a prompt syntax parser for the provided text model. - * See {@link TextModelPromptParser} for more info on the parser API. - */ - getSyntaxParserFor( - model: ITextModel, - ): TSharedPrompt & { isDisposed: false }; - - /** - * List all available prompt files. - */ - listPromptFiles(type: PromptsType, token: CancellationToken): Promise; - - /** - * Get a list of prompt source folders based on the provided prompt type. - */ - getSourceFolders(type: PromptsType): readonly IPromptPath[]; - - /** - * Returns a prompt command if the command name. - * Undefined is returned if the name does not look like a file name of a prompt file. - */ - asPromptSlashCommand(name: string): IChatPromptSlashCommand | undefined; - - /** - * Gets the prompt file for a slash command. - */ - resolvePromptSlashCommand(data: IChatPromptSlashCommand): Promise; - - /** - * Returns a prompt command if the command name is valid. - */ - findPromptSlashCommands(): Promise; - - /** - * Find all instruction files which have a glob pattern in their - * 'applyTo' metadata record that match the provided list of files. - */ - findInstructionFilesFor( - fileUris: readonly URI[], - ): Promise; - - /** - * Event that is triggered when the list of custom chat modes changes. - */ - readonly onDidChangeCustomChatModes: Event; - - /** - * Finds all available custom chat modes - */ - getCustomChatModes(): Promise; - - /** - * Gets the metadata for the given prompt file uri. - */ - getMetadata(promptFileUri: URI): Promise; - - /** - * Get all metadata for entire prompt references tree - * that spans out of each of the provided files. - * - * In other words, the metadata tree is built starting from - * each of the provided files, therefore the result is a number - * of metadata trees, one for each file. - */ - getAllMetadata( - promptUris: readonly URI[], - ): Promise; -} - -export interface IChatPromptSlashCommand { - readonly command: string; - readonly detail: string; - readonly promptPath?: IPromptPath; -} diff --git a/src/vs/base/common/objectCache.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts similarity index 98% rename from src/vs/base/common/objectCache.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts index aa50e02f5c6..443d6e44c94 100644 --- a/src/vs/base/common/objectCache.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableMap } from '../../base/common/lifecycle.js'; +import { Disposable, DisposableMap } from '../../../../../../base/common/lifecycle.js'; import { ObservableDisposable, assertNotDisposed } from './observableDisposable.js'; /** diff --git a/src/vs/base/common/observableDisposable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts similarity index 83% rename from src/vs/base/common/observableDisposable.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts index 41b7a714ddf..a0e6a70e733 100644 --- a/src/vs/base/common/observableDisposable.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableStore, IDisposable, toDisposable } from './lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; /** - * Disposable object that tracks its {@linkcode isDisposed} state - * as a public attribute and provides the {@linkcode onDispose} - * event to subscribe to. +* @deprecated do not use this, https://github.com/microsoft/vscode/issues/248366 */ export abstract class ObservableDisposable extends Disposable { /** @@ -68,16 +66,12 @@ export abstract class ObservableDisposable extends Disposable { } /** - * Type for a non-disposed object `TObject`. + * @deprecated do not use this, https://github.com/microsoft/vscode/issues/248366 */ type TNotDisposed = TObject & { isDisposed: false }; /** - * Asserts that a provided `object` is not `disposed` yet, - * e.g., its `disposed` property is `false`. - * - * @throws if the provided `object.disposed` equal to `false`. - * @param error Error message or error object to throw if assertion fails. + * @deprecated do not use this, https://github.com/microsoft/vscode/issues/248366 */ export function assertNotDisposed( object: TObject, diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index 5b7fd6f431c..ee3d3298130 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -8,20 +8,21 @@ import { assert } from '../../../../../../base/common/assert.js'; import { isAbsolute } from '../../../../../../base/common/path.js'; import { ResourceSet } from '../../../../../../base/common/map.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; -import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js'; +import { getPromptFileLocationsConfigKey, PromptsConfig } from '../config/config.js'; import { basename, dirname, joinPath } from '../../../../../../base/common/resources.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { getPromptFileExtension, getPromptFileLocationsConfigKey, getPromptFileType, PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; +import { getPromptFileExtension, getPromptFileType } from '../config/promptFileLocations.js'; +import { PromptsType } from '../promptTypes.js'; import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { getExcludes, IFileQuery, ISearchConfiguration, ISearchService, QueryType } from '../../../../../services/search/common/search.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { isCancellationError } from '../../../../../../base/common/errors.js'; -import { TPromptsStorage } from '../service/types.js'; +import { TPromptsStorage } from '../service/promptsService.js'; import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; /** * Utility class to locate prompt files. @@ -57,27 +58,47 @@ export class PromptFilesLocator extends Disposable { return files.filter(file => getPromptFileType(file) === type); } - public getFilesUpdatedEvent(type: PromptsType): Event { - const eventEmitter = this._register(new Emitter()); + public createFilesUpdatedEvent(type: PromptsType): { readonly event: Event; dispose: () => void } { + const disposables = new DisposableStore(); + const eventEmitter = disposables.add(new Emitter()); + + const userDataFolder = this.userDataService.currentProfile.promptsHome; + const key = getPromptFileLocationsConfigKey(type); - let parentFolders = this.getLocalParentFolders(type).map(folder => folder.parent); - this._register(this.configService.onDidChangeConfiguration(e => { + let parentFolders = this.getLocalParentFolders(type); + + const externalFolderWatchers = disposables.add(new DisposableStore()); + const updateExternalFolderWatchers = () => { + externalFolderWatchers.clear(); + for (const folder of parentFolders) { + if (!this.workspaceService.getWorkspaceFolder(folder.parent)) { + // if the folder is not part of the workspace, we need to watch it + const recursive = folder.filePattern !== undefined; + externalFolderWatchers.add(this.fileService.watch(folder.parent, { recursive, excludes: [] })); + } + } + }; + updateExternalFolderWatchers(); + disposables.add(this.configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(key)) { - parentFolders = this.getLocalParentFolders(type).map(folder => folder.parent); + parentFolders = this.getLocalParentFolders(type); + updateExternalFolderWatchers(); eventEmitter.fire(); } })); - this._register(this.fileService.onDidFilesChange(e => { - if (e.affects(this.userDataService.currentProfile.promptsHome)) { + disposables.add(this.fileService.onDidFilesChange(e => { + if (e.contains(userDataFolder)) { eventEmitter.fire(); return; } - if (parentFolders.some(folder => e.affects(folder))) { + if (parentFolders.some(folder => folder.filePattern !== undefined ? e.affects(folder.parent) : e.contains(folder.parent))) { eventEmitter.fire(); return; } })); - return eventEmitter.event; + disposables.add(this.fileService.watch(userDataFolder)); + + return { event: eventEmitter.event, dispose: () => disposables.dispose() }; } /** @@ -136,10 +157,7 @@ export class PromptFilesLocator extends Disposable { * * @returns List of prompt files found in the local source folders. */ - private async listFilesInLocal( - type: PromptsType, - token: CancellationToken - ): Promise { + private async listFilesInLocal(type: PromptsType, token: CancellationToken): Promise { // find all prompt files in the provided locations, then match // the found file paths against (possible) glob patterns const paths = new ResourceSet(); @@ -227,11 +245,7 @@ export class PromptFilesLocator extends Disposable { /** * Uses the search service to find all files at the provided location */ - private async searchFilesInLocation( - folder: URI, - filePattern: string | undefined, - token: CancellationToken | undefined - ): Promise { + private async searchFilesInLocation(folder: URI, filePattern: string | undefined, token: CancellationToken | undefined): Promise { const disregardIgnoreFiles = this.configService.getValue('explorer.excludeGitIgnore'); const workspaceRoot = this.workspaceService.getWorkspaceFolder(folder); @@ -267,7 +281,7 @@ export class PromptFilesLocator extends Disposable { /** * Checks if the provided `pattern` could be a valid glob pattern. */ -export const isValidGlob = (pattern: string): boolean => { +export function isValidGlob(pattern: string): boolean { let squareBrackets = false; let squareBracketsCount = 0; @@ -332,7 +346,7 @@ export const isValidGlob = (pattern: string): boolean => { } return false; -}; +} /** * Finds the first parent of the provided location that does not contain a `glob pattern`. @@ -349,9 +363,7 @@ export const isValidGlob = (pattern: string): boolean => { * ); * ``` */ -const firstNonGlobParentAndPattern = ( - location: URI -): { parent: URI; filePattern?: string } => { +function firstNonGlobParentAndPattern(location: URI): { parent: URI; filePattern?: string } { const segments = location.path.split('/'); let i = 0; while (i < segments.length && isValidGlob(segments[i]) === false) { @@ -372,7 +384,7 @@ const firstNonGlobParentAndPattern = ( parent, filePattern: segments.slice(i).join('/') }; -}; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/treeUtils.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/treeUtils.ts index 551ba3beda8..b050a59a387 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/treeUtils.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/treeUtils.ts @@ -13,9 +13,7 @@ export type TTree = { children?: readonly TTree[] } & TT /** * Flatter a tree structure into a single flat array. */ -export const flatten = ( - treeRoot: TTree, -): TTreeNode[] => { +export function flatten(treeRoot: TTree): TTreeNode[] { const result: TTreeNode[] = []; result.push(treeRoot); @@ -25,15 +23,12 @@ export const flatten = ( } return result; -}; +} /** * Traverse a tree structure and execute a callback for each node. */ -export const forEach = ( - callback: (node: TTreeNode) => boolean, - treeRoot: TTree, -): ReturnType => { +export function forEach(callback: (node: TTreeNode) => boolean, treeRoot: TTree): ReturnType { const shouldStop = callback(treeRoot); if (shouldStop === true) { @@ -49,7 +44,7 @@ export const forEach = ( } return false; -}; +} /** * Maps nodes of a tree to a new type preserving the original tree structure by invoking @@ -91,16 +86,16 @@ export const forEach = ( * }); * ``` */ -export const map = < +export function map< TTreeNode extends object, - TNewTreeNode extends object, + TNewTreeNode extends object >( callback: ( originalNode: Readonly>, newChildren: Readonly[] | undefined, ) => TTree, treeRoot: TTree, -): TTree => { +): TTree { // if the node does not have children, just call the callback if (treeRoot.children === undefined) { return callback(treeRoot, undefined); @@ -123,7 +118,7 @@ export const map = < newNode.children = newChildren; return newNode; -}; +} /** * Type for a generic comparable object - the one that implements @@ -188,10 +183,7 @@ type TDiffTree = TTree & { * of the same type. The result is another tree of difference * nodes that represent difference between tree node pairs. */ -export function difference>( - tree1: TTree>, - tree2: TTree>, -): TDiffTree | null { +export function difference>(tree1: TTree>, tree2: TTree>): TDiffTree | null { const tree1Children = tree1.children ?? []; const tree2Children = tree2.children ?? []; @@ -276,11 +268,11 @@ type TCurriedFunction unknown> = ((...args: TRestP /** * Curry a provided function with the first argument. */ -export const curry = ( +export function curry( callback: (arg1: T, ...args: any[]) => K, arg1: T, -): TCurriedFunction => { +): TCurriedFunction { return (...args) => { return callback(arg1, ...args); }; -}; +} diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/chatDeveloperActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/chatDeveloperActions.ts similarity index 100% rename from src/vs/workbench/contrib/chat/electron-sandbox/actions/chatDeveloperActions.ts rename to src/vs/workbench/contrib/chat/electron-browser/actions/chatDeveloperActions.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-browser/actions/media/voiceChatActions.css similarity index 100% rename from src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css rename to src/vs/workbench/contrib/chat/electron-browser/actions/media/voiceChatActions.css diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts similarity index 100% rename from src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts rename to src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts rename to src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts similarity index 100% rename from src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts rename to src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts index 0d5400e2fa0..9e65f6630a2 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts @@ -26,7 +26,7 @@ import { NotebookTextModel } from '../../../notebook/common/model/notebookTextMo import { INotebookService } from '../../../notebook/common/notebookService.js'; import { ChatEditingService } from '../../browser/chatEditing/chatEditingServiceImpl.js'; import { ChatAgentService, IChatAgentData, IChatAgentImplementation, IChatAgentService } from '../../common/chatAgents.js'; -import { IChatEditingService } from '../../common/chatEditingService.js'; +import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatService } from '../../common/chatService.js'; import { ChatService } from '../../common/chatServiceImpl.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; @@ -36,6 +36,12 @@ import { ChatAgentLocation, ChatMode } from '../../common/constants.js'; import { ILanguageModelsService } from '../../common/languageModels.js'; import { NullLanguageModelsService } from '../common/languageModels.js'; import { MockChatVariablesService } from '../common/mockChatVariables.js'; +import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; +import { TestWorkerService } from '../../../inlineChat/test/browser/testWorkerService.js'; +import { EditOperation } from '../../../../../editor/common/core/editOperation.js'; +import { Position } from '../../../../../editor/common/core/position.js'; +import { ChatModel } from '../../common/chatModel.js'; +import { TextEdit } from '../../../../../editor/common/languages.js'; function getAgentData(id: string): IChatAgentData { return { @@ -68,6 +74,7 @@ suite('ChatEditingService', function () { collection.set(IChatSlashCommandService, new class extends mock() { }); collection.set(IChatTransferService, new SyncDescriptor(ChatTransferService)); collection.set(IChatEditingService, new SyncDescriptor(ChatEditingService)); + collection.set(IEditorWorkerService, new SyncDescriptor(TestWorkerService)); collection.set(IChatService, new SyncDescriptor(ChatService)); collection.set(ILanguageModelsService, new SyncDescriptor(NullLanguageModelsService)); collection.set(IMultiDiffSourceResolverService, new class extends mock() { @@ -84,6 +91,7 @@ suite('ChatEditingService', function () { } }); const insta = store.add(store.add(workbenchInstantiationService(undefined, store)).createChild(collection)); + store.add(insta.get(IEditorWorkerService) as TestWorkerService); const value = insta.get(IChatEditingService); assert.ok(value instanceof ChatEditingService); editingService = value; @@ -106,7 +114,7 @@ suite('ChatEditingService', function () { store.add(textModelService.registerTextModelContentProvider('test', { async provideTextContent(resource) { - return modelService.createModel(resource.path.repeat(10), null, resource, false); + return store.add(modelService.createModel(resource.path.repeat(10), null, resource, false)); }, })); }); @@ -135,7 +143,6 @@ suite('ChatEditingService', function () { model.dispose(); }); - test('create session, file entry & isCurrentlyBeingModifiedBy', async function () { assert.ok(editingService); @@ -166,9 +173,124 @@ suite('ChatEditingService', function () { await unset; - await entry.reject(undefined); + await entry.reject(); model.dispose(); }); + async function idleAfterEdit(session: IChatEditingSession, model: ChatModel, uri: URI, edits: TextEdit[]) { + const isStreaming = waitForState(session.state.map(s => s === ChatEditingSessionState.StreamingEdits), Boolean); + + const chatRequest = model.addRequest({ text: '', parts: [] }, { variables: [] }, 0); + assertType(chatRequest.response); + + chatRequest.response.updateContent({ kind: 'textEdit', uri, edits, done: true }); + + const entry = await waitForState(session.entries.map(value => value.find(a => isEqual(a.modifiedURI, uri)))); + + assert.ok(isEqual(entry.modifiedURI, uri)); + + chatRequest.response.complete(); + + await isStreaming; + + const isIdle = waitForState(session.state.map(s => s === ChatEditingSessionState.Idle), Boolean); + await isIdle; + + return entry; + } + + test('mirror typing outside -> accept', async function () { + assert.ok(editingService); + + const uri = URI.from({ scheme: 'test', path: 'abc\n' }); + + const model = store.add(chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const session = await model.editingSessionObs?.promise; + assertType(session, 'session not created'); + + const entry = await idleAfterEdit(session, model, uri, [{ range: new Range(1, 1, 1, 1), text: 'FarBoo\n' }]); + const original = store.add(await textModelService.createModelReference(entry.originalURI)).object.textEditorModel; + const modified = store.add(await textModelService.createModelReference(entry.modifiedURI)).object.textEditorModel; + + assert.strictEqual(entry.state.get(), ModifiedFileEntryState.Modified); + + assert.strictEqual(original.getValue(), 'abc\n'.repeat(10)); + assert.strictEqual(modified.getValue(), 'FarBoo\n' + 'abc\n'.repeat(10)); + + modified.pushEditOperations(null, [EditOperation.insert(new Position(3, 1), 'USER_TYPE\n')], () => null); + + assert.ok(modified.getValue().includes('USER_TYPE')); + assert.ok(original.getValue().includes('USER_TYPE')); + + await entry.accept(); + assert.strictEqual(modified.getValue(), original.getValue()); + assert.strictEqual(entry.state.get(), ModifiedFileEntryState.Accepted); + + assert.ok(modified.getValue().includes('FarBoo')); + assert.ok(original.getValue().includes('FarBoo')); + }); + + test('mirror typing outside -> reject', async function () { + assert.ok(editingService); + + const uri = URI.from({ scheme: 'test', path: 'abc\n' }); + + const model = store.add(chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const session = await model.editingSessionObs?.promise; + assertType(session, 'session not created'); + + const entry = await idleAfterEdit(session, model, uri, [{ range: new Range(1, 1, 1, 1), text: 'FarBoo\n' }]); + const original = store.add(await textModelService.createModelReference(entry.originalURI)).object.textEditorModel; + const modified = store.add(await textModelService.createModelReference(entry.modifiedURI)).object.textEditorModel; + + assert.strictEqual(entry.state.get(), ModifiedFileEntryState.Modified); + + assert.strictEqual(original.getValue(), 'abc\n'.repeat(10)); + assert.strictEqual(modified.getValue(), 'FarBoo\n' + 'abc\n'.repeat(10)); + + modified.pushEditOperations(null, [EditOperation.insert(new Position(3, 1), 'USER_TYPE\n')], () => null); + + assert.ok(modified.getValue().includes('USER_TYPE')); + assert.ok(original.getValue().includes('USER_TYPE')); + + await entry.reject(); + assert.strictEqual(modified.getValue(), original.getValue()); + assert.strictEqual(entry.state.get(), ModifiedFileEntryState.Rejected); + + assert.ok(!modified.getValue().includes('FarBoo')); + assert.ok(!original.getValue().includes('FarBoo')); + }); + + test('NO mirror typing inside -> accept', async function () { + assert.ok(editingService); + + const uri = URI.from({ scheme: 'test', path: 'abc\n' }); + + const model = store.add(chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const session = await model.editingSessionObs?.promise; + assertType(session, 'session not created'); + + const entry = await idleAfterEdit(session, model, uri, [{ range: new Range(1, 1, 1, 1), text: 'FarBoo\n' }]); + const original = store.add(await textModelService.createModelReference(entry.originalURI)).object.textEditorModel; + const modified = store.add(await textModelService.createModelReference(entry.modifiedURI)).object.textEditorModel; + + assert.strictEqual(entry.state.get(), ModifiedFileEntryState.Modified); + + assert.strictEqual(original.getValue(), 'abc\n'.repeat(10)); + assert.strictEqual(modified.getValue(), 'FarBoo\n' + 'abc\n'.repeat(10)); + + modified.pushEditOperations(null, [EditOperation.replace(new Range(1, 2, 1, 7), 'ooBar')], () => null); + + assert.ok(modified.getValue().includes('FooBar')); + assert.ok(!original.getValue().includes('FooBar')); // typed in the AI edits, DO NOT transpose + + await entry.accept(); + assert.strictEqual(modified.getValue(), original.getValue()); + assert.strictEqual(entry.state.get(), ModifiedFileEntryState.Accepted); + + assert.ok(modified.getValue().includes('FooBar')); + assert.ok(original.getValue().includes('FooBar')); + }); + }); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts index cd07b1d1c9d..7777341548b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts @@ -9,16 +9,13 @@ import { cloneAndChange } from '../../../../../base/common/objects.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { StringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; -import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; import { FileService } from '../../../../../platform/files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; import { TestEnvironmentService } from '../../../../test/browser/workbenchTestServices.js'; -import { ISnapshotEntry } from '../../browser/chatEditing/chatEditingModifiedFileEntry.js'; import { ChatEditingSessionStorage, IChatEditingSessionStop, StoredSessionState } from '../../browser/chatEditing/chatEditingSessionStorage.js'; import { ChatEditingSnapshotTextModelContentProvider } from '../../browser/chatEditing/chatEditingTextModelContentProviders.js'; -import { ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; suite('ChatEditingSessionStorage', () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); @@ -51,7 +48,7 @@ suite('ChatEditingSessionStorage', () => { return { stopId, entries: new ResourceMap([ - [resource, { resource, languageId: 'javascript', snapshotUri: ChatEditingSnapshotTextModelContentProvider.getSnapshotFileURI(sessionId, requestId, stopId, resource.path), original: `contents${before}}`, current: `contents${after}`, originalToCurrentEdit: StringEdit.replace(OffsetRange.ofLength(42), 'newtext'), state: ModifiedFileEntryState.Modified, telemetryInfo: { agentId: 'agentId', command: 'cmd', requestId: generateUuid(), result: undefined, sessionId } } satisfies ISnapshotEntry], + [resource, { resource, languageId: 'javascript', snapshotUri: ChatEditingSnapshotTextModelContentProvider.getSnapshotFileURI(sessionId, requestId, stopId, resource.path), original: `contents${before}}`, current: `contents${after}`, state: ModifiedFileEntryState.Modified, telemetryInfo: { agentId: 'agentId', command: 'cmd', requestId: generateUuid(), result: undefined, sessionId } } satisfies ISnapshotEntry], ]), }; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatSelectedTools.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatSelectedTools.test.ts new file mode 100644 index 00000000000..43cf96af5ae --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/chatSelectedTools.test.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js'; +import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js'; +import { IChatService } from '../../common/chatService.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../common/languageModelToolsService.js'; +import { MockChatService } from '../common/mockChatService.js'; +import { ChatSelectedTools } from '../../browser/chatSelectedTools.js'; +import { constObservable } from '../../../../../base/common/observable.js'; +import { ChatMode } from '../../common/constants.js'; +import { Iterable } from '../../../../../base/common/iterator.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; +import { timeout } from '../../../../../base/common/async.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; + +suite('ChatSelectedTools', () => { + + let store: DisposableStore; + + let toolsService: ILanguageModelToolsService; + let selectedTools: ChatSelectedTools; + + setup(() => { + + store = new DisposableStore(); + + const instaService = workbenchInstantiationService({ + contextKeyService: () => store.add(new ContextKeyService(new TestConfigurationService)), + }, store); + instaService.stub(IChatService, new MockChatService()); + instaService.stub(ILanguageModelToolsService, instaService.createInstance(LanguageModelToolsService)); + + store.add(instaService); + toolsService = instaService.get(ILanguageModelToolsService); + selectedTools = store.add(instaService.createInstance(ChatSelectedTools, constObservable(ChatMode.Agent))); + }); + + teardown(function () { + store.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('Can\'t enable/disable MCP tools directly #18161', () => { + + return runWithFakedTimers({}, async () => { + + const toolData1: IToolData = { + id: 'testTool1', + modelDescription: 'Test Tool 1', + displayName: 'Test Tool 1', + canBeReferencedInPrompt: true, + toolReferenceName: 't1', + source: ToolDataSource.Internal, + }; + + const toolData2: IToolData = { + id: 'testTool2', + modelDescription: 'Test Tool 2', + displayName: 'Test Tool 2', + source: ToolDataSource.Internal, + canBeReferencedInPrompt: true, + toolReferenceName: 't2', + }; + + const toolData3: IToolData = { + id: 'testTool3', + modelDescription: 'Test Tool 3', + displayName: 'Test Tool 3', + source: ToolDataSource.Internal, + canBeReferencedInPrompt: true, + toolReferenceName: 't3', + }; + + const toolset = toolsService.createToolSet( + ToolDataSource.Internal, + 'mcp', 'mcp' + ); + + store.add(toolsService.registerToolData(toolData1)); + store.add(toolsService.registerToolData(toolData2)); + store.add(toolsService.registerToolData(toolData3)); + + store.add(toolset); + store.add(toolset.addTool(toolData1)); + store.add(toolset.addTool(toolData2)); + store.add(toolset.addTool(toolData3)); + + assert.strictEqual(Iterable.length(toolsService.getTools()), 3); + + const size = Iterable.length(toolset.getTools()); + assert.strictEqual(size, 3); + + await timeout(1000); // UGLY the tools service updates its state sync but emits the event async (750ms) delay. This affects the observable that depends on the event + + assert.strictEqual(selectedTools.entriesMap.size, 4); // 1 toolset, 3 tools + + selectedTools.disable([], [toolData2, toolData3], false); + + const map = selectedTools.asEnablementMap(); + assert.strictEqual(map.size, 3); // 3 tools + + assert.strictEqual(map.get(toolData1), true); + assert.strictEqual(map.get(toolData2), false); + assert.strictEqual(map.get(toolData3), false); + }); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 217ec965f1d..5c2da359ca5 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -20,7 +20,7 @@ import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { IChatVariablesService } from '../../common/chatVariables.js'; import { ChatMode, ChatAgentLocation } from '../../common/constants.js'; import { IToolData, ToolDataSource } from '../../common/languageModelToolsService.js'; -import { IPromptsService } from '../../common/promptSyntax/service/types.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { MockChatService } from './mockChatService.js'; import { MockPromptsService } from './mockPromptsService.js'; diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index ae5cdcf211a..810e953918e 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -6,9 +6,10 @@ import { Event } from '../../../../../base/common/event.js'; import { URI } from '../../../../../base/common/uri.js'; import { ITextModel } from '../../../../../editor/common/model.js'; -import { PromptsType } from '../../../../../platform/prompts/common/prompts.js'; +import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { TextModelPromptParser } from '../../common/promptSyntax/parsers/textModelPromptParser.js'; -import { IChatPromptSlashCommand, ICustomChatMode, IMetadata, IPromptPath, IPromptsService } from '../../common/promptSyntax/service/types.js'; +import { IChatPromptSlashCommand, ICustomChatMode, IMetadata, IPromptParserResult, IPromptPath, IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; export class MockPromptsService implements IPromptsService { @@ -42,7 +43,10 @@ export class MockPromptsService implements IPromptsService { throw new Error('Method not implemented.'); } onDidChangeCustomChatModes: Event = Event.None; - getCustomChatModes(): Promise { + getCustomChatModes(token: CancellationToken): Promise { + throw new Error('Method not implemented.'); + } + parse(uri: URI, token: CancellationToken): Promise { throw new Error('Method not implemented.'); } dispose(): void { } diff --git a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterBoolean.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts similarity index 83% rename from src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterBoolean.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts index 45c7b430d17..8d32c30d851 100644 --- a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterBoolean.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { Word } from '../../../../common/codecs/simpleCodec/tokens/index.js'; -import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; -import { FrontMatterBoolean } from '../../../../common/codecs/frontMatterCodec/tokens/index.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { FrontMatterSequence } from '../../../../common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; +import { randomBoolean } from '../../../../../../../../../base/test/common/testUtils.js'; +import { FrontMatterBoolean } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; suite('FrontMatterBoolean', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• equals()', () => { - suite('• base case', () => { - test('• true', () => { + suite('equals()', () => { + suite('base case', () => { + test('true', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'true' @@ -48,7 +48,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• false', () => { + test('false', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'false' @@ -81,9 +81,9 @@ suite('FrontMatterBoolean', () => { }); }); - suite('• non-boolean token', () => { - suite('• word token', () => { - test('• true', () => { + suite('non-boolean token', () => { + suite('word token', () => { + test('true', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'true' @@ -107,7 +107,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• false', () => { + test('false', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'false' @@ -132,8 +132,8 @@ suite('FrontMatterBoolean', () => { }); }); - suite('• sequence token', () => { - test('• true', () => { + suite('sequence token', () => { + test('true', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'true' @@ -159,7 +159,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• false', () => { + test('false', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'false' @@ -187,8 +187,8 @@ suite('FrontMatterBoolean', () => { }); }); - suite('• different range', () => { - test('• true', () => { + suite('different range', () => { + test('true', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'true' @@ -214,7 +214,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• false', () => { + test('false', () => { // both values should yield the same result const booleanText = (randomBoolean()) ? 'false' @@ -241,8 +241,8 @@ suite('FrontMatterBoolean', () => { }); }); - suite('• different text', () => { - test('• true', () => { + suite('different text', () => { + test('true', () => { const boolean = new FrontMatterBoolean( new Word( new Range(1, 1, 1, 5), @@ -263,7 +263,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• false', () => { + test('false', () => { const boolean = new FrontMatterBoolean( new Word( new Range(5, 15, 5, 15 + 6), @@ -285,7 +285,7 @@ suite('FrontMatterBoolean', () => { }); }); - test('• throws if cannot be converted to a boolean', () => { + test('throws if cannot be converted to a boolean', () => { assert.throws(() => { new FrontMatterBoolean( new Word( diff --git a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts similarity index 87% rename from src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterDecoder.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts index 9bd7164dd5c..b4f78b9d19b 100644 --- a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterDecoder.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ -import { Range } from '../../../../common/core/range.js'; -import { TestDecoder } from '../../utils/testDecoder.js'; -import { VSBuffer } from '../../../../../base/common/buffer.js'; -import { newWriteableStream } from '../../../../../base/common/stream.js'; -import { NewLine } from '../../../../common/codecs/linesCodec/tokens/newLine.js'; -import { DoubleQuote } from '../../../../common/codecs/simpleCodec/tokens/doubleQuote.js'; -import { type TSimpleDecoderToken } from '../../../../common/codecs/simpleCodec/simpleDecoder.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { LeftBracket, RightBracket } from '../../../../common/codecs/simpleCodec/tokens/brackets.js'; -import { FrontMatterDecoder } from '../../../../common/codecs/frontMatterCodec/frontMatterDecoder.js'; -import { FrontMatterSequence } from '../../../../common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; -import { ExclamationMark, Quote, Tab, Word, Space, Colon, VerticalTab, Comma, Dash } from '../../../../common/codecs/simpleCodec/tokens/index.js'; -import { FrontMatterBoolean, FrontMatterString, FrontMatterArray, FrontMatterRecord, FrontMatterRecordDelimiter, FrontMatterRecordName } from '../../../../common/codecs/frontMatterCodec/tokens/index.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { TestDecoder } from '../utils/testDecoder.js'; +import { VSBuffer } from '../../../../../../../../../base/common/buffer.js'; +import { newWriteableStream } from '../../../../../../../../../base/common/stream.js'; +import { NewLine } from '../../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; +import { DoubleQuote } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.js'; +import { type TSimpleDecoderToken } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { LeftBracket, RightBracket } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.js'; +import { FrontMatterDecoder } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.js'; +import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; +import { ExclamationMark, Quote, Tab, Word, Space, Colon, VerticalTab, Comma, Dash } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; +import { FrontMatterBoolean, FrontMatterString, FrontMatterArray, FrontMatterRecord, FrontMatterRecordDelimiter, FrontMatterRecordName } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; /** * Front Matter decoder for testing purposes. @@ -33,7 +33,7 @@ export class TestFrontMatterDecoder extends TestDecoder { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); - test('• produces expected tokens', async () => { + test('produces expected tokens', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -115,9 +115,9 @@ suite('FrontMatterDecoder', () => { ]); }); - suite('• record', () => { - suite('• values', () => { - test('• unquoted string', async () => { + suite('record', () => { + suite('values', () => { + test('unquoted string', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -164,7 +164,7 @@ suite('FrontMatterDecoder', () => { ]); }); - test('• quoted string', async () => { + test('quoted string', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -219,7 +219,7 @@ suite('FrontMatterDecoder', () => { ]); }); - test('• boolean', async () => { + test('boolean', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -269,8 +269,8 @@ suite('FrontMatterDecoder', () => { ]); }); - suite('• array', () => { - test('• empty', async () => { + suite('array', () => { + test('empty', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -314,7 +314,7 @@ suite('FrontMatterDecoder', () => { ]); }); - test('• mixed values', async () => { + test('mixed values', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -358,7 +358,7 @@ suite('FrontMatterDecoder', () => { ]); }); - test('• redundant commas', async () => { + test('redundant commas', async () => { const test = disposables.add(new TestFrontMatterDecoder()); await test.run( @@ -405,7 +405,7 @@ suite('FrontMatterDecoder', () => { }); }); - test('• empty', async () => { + test('empty', async () => { const test = disposables.add( new TestFrontMatterDecoder(), ); diff --git a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterRecord.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts similarity index 86% rename from src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterRecord.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts index 1216324289a..fba57e45209 100644 --- a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterRecord.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { FrontMatterSequence } from '../../../../common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; -import { Colon, LeftBracket, Quote, RightBracket, Space, Tab, VerticalTab, Word } from '../../../../common/codecs/simpleCodec/tokens/index.js'; -import { FrontMatterArray, FrontMatterBoolean, FrontMatterRecord, FrontMatterRecordDelimiter, FrontMatterRecordName, FrontMatterString } from '../../../../common/codecs/frontMatterCodec/tokens/index.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; +import { Colon, LeftBracket, Quote, RightBracket, Space, Tab, VerticalTab, Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; +import { FrontMatterArray, FrontMatterBoolean, FrontMatterRecord, FrontMatterRecordDelimiter, FrontMatterRecordName, FrontMatterString } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; suite('FrontMatterBoolean', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• trimValueEnd()', () => { - test('• trims space tokens at the end of record\'s value', () => { + suite('trimValueEnd()', () => { + test('trims space tokens at the end of record\'s value', () => { const recordName = new FrontMatterRecordName([ new Word( new Range(4, 10, 4, 10 + 3), @@ -59,8 +59,8 @@ suite('FrontMatterBoolean', () => { ); }); - suite('• does not trim non-sequence value tokens', () => { - test('• boolean', () => { + suite('does not trim non-sequence value tokens', () => { + test('boolean', () => { const recordName = new FrontMatterRecordName([ new Word( new Range(4, 10, 4, 10 + 3), @@ -96,7 +96,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• quoted string', () => { + test('quoted string', () => { const recordName = new FrontMatterRecordName([ new Word( new Range(4, 10, 4, 10 + 3), @@ -134,7 +134,7 @@ suite('FrontMatterBoolean', () => { ); }); - test('• array', () => { + test('array', () => { const recordName = new FrontMatterRecordName([ new Word( new Range(4, 10, 4, 10 + 3), diff --git a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterSequence.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts similarity index 76% rename from src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterSequence.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts index 9f752d17698..68602622bba 100644 --- a/src/vs/editor/test/common/codecs/frontMatterDecoder/frontMatterSequence.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { FrontMatterValueToken } from '../../../../common/codecs/frontMatterCodec/tokens/index.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { Space, Tab, VerticalTab, Word } from '../../../../common/codecs/simpleCodec/tokens/index.js'; -import { FrontMatterSequence } from '../../../../common/codecs/frontMatterCodec/tokens/frontMatterSequence.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { FrontMatterValueToken } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { Space, Tab, VerticalTab, Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; +import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; suite('FrontMatterSequence', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('• extends \'FrontMatterValueToken\'', () => { + test('extends \'FrontMatterValueToken\'', () => { const sequence = new FrontMatterSequence([ new Word( new Range(1, 1, 1, 5), @@ -27,8 +27,8 @@ suite('FrontMatterSequence', () => { ); }); - suite('• trimEnd()', () => { - test('• trims space tokens at the end of the sequence', () => { + suite('trimEnd()', () => { + test('trims space tokens at the end of the sequence', () => { const sequence = new FrontMatterSequence([ new Word(new Range(4, 18, 4, 18 + 10), 'some-value'), new Space(new Range(4, 28, 4, 29)), @@ -59,7 +59,7 @@ suite('FrontMatterSequence', () => { ); }); - test('• remains functional if only spacing tokens were present', () => { + test('remains functional if only spacing tokens were present', () => { const sequence = new FrontMatterSequence([ new Space(new Range(4, 28, 4, 29)), new Space(new Range(4, 29, 4, 30)), @@ -98,7 +98,7 @@ suite('FrontMatterSequence', () => { }); }); - test('• throws if no tokens provided', () => { + test('throws if no tokens provided', () => { assert.throws(() => { new FrontMatterSequence([]); }); diff --git a/src/vs/editor/test/common/codecs/linesDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts similarity index 82% rename from src/vs/editor/test/common/codecs/linesDecoder.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts index 82ff987120a..b9ac4916923 100644 --- a/src/vs/editor/test/common/codecs/linesDecoder.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../common/core/range.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { Line } from '../../../common/codecs/linesCodec/tokens/line.js'; -import { TestDecoder, TTokensConsumeMethod } from '../utils/testDecoder.js'; -import { NewLine } from '../../../common/codecs/linesCodec/tokens/newLine.js'; -import { newWriteableStream, WriteableStream } from '../../../../base/common/stream.js'; -import { CarriageReturn } from '../../../common/codecs/linesCodec/tokens/carriageReturn.js'; -import { LinesDecoder, TLineToken } from '../../../common/codecs/linesCodec/linesDecoder.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { Range } from '../../../../../../../../editor/common/core/range.js'; +import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; +import { DisposableStore } from '../../../../../../../../base/common/lifecycle.js'; +import { Line } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/line.js'; +import { TestDecoder, TTokensConsumeMethod } from './utils/testDecoder.js'; +import { NewLine } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; +import { newWriteableStream, WriteableStream } from '../../../../../../../../base/common/stream.js'; +import { CarriageReturn } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; +import { LinesDecoder, TLineToken } from '../../../../../common/promptSyntax/codecs/base/linesCodec/linesDecoder.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; /** * Note! This decoder is also often used to test common logic of abstract {@link BaseDecoder} @@ -26,14 +26,14 @@ suite('LinesDecoder', () => { * Test the core logic with specific method of consuming * tokens that are produced by a lines decoder instance. */ - suite('• core logic', () => { + suite('core logic', () => { testLinesDecoder('async-generator', disposables); testLinesDecoder('consume-all-method', disposables); testLinesDecoder('on-data-event', disposables); }); - suite('• settled promise', () => { - test('• throws if accessed on not-yet-started decoder instance', () => { + suite('settled promise', () => { + test('throws if accessed on not-yet-started decoder instance', () => { const test = disposables.add(new TestLinesDecoder()); assert.throws( @@ -51,8 +51,8 @@ suite('LinesDecoder', () => { }); }); - suite('• start', () => { - test('• throws if the decoder object is already `disposed`', () => { + suite('start', () => { + test('throws if the decoder object is already `disposed`', () => { const test = disposables.add(new TestLinesDecoder()); const { decoder } = test; decoder.dispose(); @@ -63,7 +63,7 @@ suite('LinesDecoder', () => { ); }); - test('• throws if the decoder object is already `ended`', async () => { + test('throws if the decoder object is already `ended`', async () => { const inputStream = newWriteableStream(null); const test = disposables.add(new TestLinesDecoder(inputStream)); const { decoder } = test; @@ -141,8 +141,8 @@ function testLinesDecoder( disposables: Pick, ) { suite(tokensConsumeMethod, () => { - suite('• produces expected tokens', () => { - test('• input starts with line data', async () => { + suite('produces expected tokens', () => { + test('input starts with line data', async () => { const test = disposables.add(new TestLinesDecoder()); await test.run( @@ -160,7 +160,7 @@ function testLinesDecoder( ); }); - test('• standalone \\r is treated as new line', async () => { + test('standalone \\r is treated as new line', async () => { const test = disposables.add(new TestLinesDecoder()); await test.run( @@ -179,7 +179,7 @@ function testLinesDecoder( ); }); - test('• input starts with a new line', async () => { + test('input starts with a new line', async () => { const test = disposables.add(new TestLinesDecoder()); await test.run( @@ -202,7 +202,7 @@ function testLinesDecoder( ); }); - test('• input starts and ends with multiple new lines', async () => { + test('input starts and ends with multiple new lines', async () => { const test = disposables.add(new TestLinesDecoder()); await test.run( @@ -229,7 +229,7 @@ function testLinesDecoder( ); }); - test('• single carriage return is treated as new line', async () => { + test('single carriage return is treated as new line', async () => { const test = disposables.add(new TestLinesDecoder()); await test.run( diff --git a/src/vs/editor/test/common/codecs/markdownDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts similarity index 91% rename from src/vs/editor/test/common/codecs/markdownDecoder.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts index 2219068bef3..1c9a0c0abbf 100644 --- a/src/vs/editor/test/common/codecs/markdownDecoder.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts @@ -4,28 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { TestDecoder } from '../utils/testDecoder.js'; -import { Range } from '../../../common/core/range.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { newWriteableStream } from '../../../../base/common/stream.js'; -import { Tab } from '../../../common/codecs/simpleCodec/tokens/tab.js'; -import { Word } from '../../../common/codecs/simpleCodec/tokens/word.js'; -import { Dash } from '../../../common/codecs/simpleCodec/tokens/dash.js'; -import { Space } from '../../../common/codecs/simpleCodec/tokens/space.js'; -import { Slash } from '../../../common/codecs/simpleCodec/tokens/slash.js'; -import { NewLine } from '../../../common/codecs/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../../../common/codecs/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../../../common/codecs/simpleCodec/tokens/verticalTab.js'; -import { MarkdownLink } from '../../../common/codecs/markdownCodec/tokens/markdownLink.js'; -import { CarriageReturn } from '../../../common/codecs/linesCodec/tokens/carriageReturn.js'; -import { MarkdownImage } from '../../../common/codecs/markdownCodec/tokens/markdownImage.js'; -import { ExclamationMark } from '../../../common/codecs/simpleCodec/tokens/exclamationMark.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; -import { MarkdownComment } from '../../../common/codecs/markdownCodec/tokens/markdownComment.js'; -import { LeftBracket, RightBracket } from '../../../common/codecs/simpleCodec/tokens/brackets.js'; -import { MarkdownDecoder, TMarkdownToken } from '../../../common/codecs/markdownCodec/markdownDecoder.js'; -import { LeftParenthesis, RightParenthesis } from '../../../common/codecs/simpleCodec/tokens/parentheses.js'; -import { LeftAngleBracket, RightAngleBracket } from '../../../common/codecs/simpleCodec/tokens/angleBrackets.js'; +import { TestDecoder } from './utils/testDecoder.js'; +import { Range } from '../../../../../../../../editor/common/core/range.js'; +import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; +import { newWriteableStream } from '../../../../../../../../base/common/stream.js'; +import { Tab } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tab.js'; +import { Word } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/word.js'; +import { Dash } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/dash.js'; +import { Space } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/space.js'; +import { Slash } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/slash.js'; +import { NewLine } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; +import { FormFeed } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.js'; +import { VerticalTab } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.js'; +import { MarkdownLink } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.js'; +import { CarriageReturn } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; +import { MarkdownImage } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownImage.js'; +import { ExclamationMark } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; +import { MarkdownComment } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownComment.js'; +import { LeftBracket, RightBracket } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.js'; +import { MarkdownDecoder, TMarkdownToken } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/markdownDecoder.js'; +import { LeftParenthesis, RightParenthesis } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.js'; +import { LeftAngleBracket, RightAngleBracket } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.js'; /** * A reusable test utility that asserts that a `TestMarkdownDecoder` instance @@ -61,8 +61,8 @@ export class TestMarkdownDecoder extends TestDecoder { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); - suite('• general', () => { - test('• base cases', async () => { + suite('general', () => { + test('base cases', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -127,7 +127,7 @@ suite('MarkdownDecoder', () => { ); }); - test('• nuanced', async () => { + test('nuanced', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -174,9 +174,9 @@ suite('MarkdownDecoder', () => { }); }); - suite('• links', () => { - suite('• broken', () => { - test('• invalid', async () => { + suite('links', () => { + suite('broken', () => { + test('invalid', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -226,7 +226,7 @@ suite('MarkdownDecoder', () => { ); }); - suite('• stop characters inside caption/reference (new lines)', () => { + suite('stop characters inside caption/reference (new lines)', () => { for (const StopCharacter of [CarriageReturn, NewLine]) { let characterName = ''; @@ -318,7 +318,7 @@ suite('MarkdownDecoder', () => { /** * Same as above but these stop characters do not move the caret to the next line. */ - suite('• stop characters inside caption/reference (same line)', () => { + suite('stop characters inside caption/reference (same line)', () => { for (const StopCharacter of [VerticalTab, FormFeed]) { let characterName = ''; @@ -410,9 +410,9 @@ suite('MarkdownDecoder', () => { }); - suite('• images', () => { - suite('• general', () => { - test('• base cases', async () => { + suite('images', () => { + suite('general', () => { + test('base cases', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -456,7 +456,7 @@ suite('MarkdownDecoder', () => { ); }); - test('• nuanced', async () => { + test('nuanced', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -494,8 +494,8 @@ suite('MarkdownDecoder', () => { }); }); - suite('• broken', () => { - test('• invalid', async () => { + suite('broken', () => { + test('invalid', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -567,7 +567,7 @@ suite('MarkdownDecoder', () => { ); }); - suite('• stop characters inside caption/reference (new lines)', () => { + suite('stop characters inside caption/reference (new lines)', () => { for (const StopCharacter of [CarriageReturn, NewLine]) { let characterName = ''; @@ -662,7 +662,7 @@ suite('MarkdownDecoder', () => { /** * Same as above but these stop characters do not move the caret to the next line. */ - suite('• stop characters inside caption/reference (same line)', () => { + suite('stop characters inside caption/reference (same line)', () => { for (const stopCharacter of [VerticalTab, FormFeed]) { let characterName = ''; @@ -756,9 +756,9 @@ suite('MarkdownDecoder', () => { }); }); - suite('• comments', () => { - suite('• general', () => { - test('• base cases', async () => { + suite('comments', () => { + suite('general', () => { + test('base cases', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -818,7 +818,7 @@ suite('MarkdownDecoder', () => { ); }); - test('• nuanced', async () => { + test('nuanced', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); @@ -866,7 +866,7 @@ suite('MarkdownDecoder', () => { }); }); - test('• invalid', async () => { + test('invalid', async () => { const test = testDisposables.add( new TestMarkdownDecoder(), ); diff --git a/src/vs/editor/test/common/codecs/simpleDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/simpleDecoder.test.ts similarity index 90% rename from src/vs/editor/test/common/codecs/simpleDecoder.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/simpleDecoder.test.ts index 7ebb92e68a0..68beddf42f6 100644 --- a/src/vs/editor/test/common/codecs/simpleDecoder.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/simpleDecoder.test.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Range } from '../../../common/core/range.js'; -import { TestDecoder } from '../utils/testDecoder.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { newWriteableStream } from '../../../../base/common/stream.js'; -import { NewLine } from '../../../common/codecs/linesCodec/tokens/newLine.js'; -import { CarriageReturn } from '../../../common/codecs/linesCodec/tokens/carriageReturn.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; -import { SimpleDecoder, TSimpleDecoderToken } from '../../../common/codecs/simpleCodec/simpleDecoder.js'; +import { Range } from '../../../../../../../../editor/common/core/range.js'; +import { TestDecoder } from './utils/testDecoder.js'; +import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; +import { newWriteableStream } from '../../../../../../../../base/common/stream.js'; +import { NewLine } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; +import { CarriageReturn } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; +import { SimpleDecoder, TSimpleDecoderToken } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.js'; import { At, Tab, @@ -35,7 +35,7 @@ import { LeftAngleBracket, RightAngleBracket, Comma, -} from '../../../common/codecs/simpleCodec/tokens/index.js'; +} from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; /** * A reusable test utility that asserts that a `SimpleDecoder` instance diff --git a/src/vs/editor/test/common/codecs/testUtils/randomRange.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomRange.ts similarity index 78% rename from src/vs/editor/test/common/codecs/testUtils/randomRange.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomRange.ts index c9e589c5f0a..db00f6920d7 100644 --- a/src/vs/editor/test/common/codecs/testUtils/randomRange.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomRange.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { randomInt } from '../../../../../base/common/numbers.js'; -import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { randomInt } from '../../../../../../../../../base/common/numbers.js'; +import { randomBoolean } from '../../../../../../../../../base/test/common/testUtils.js'; /** * Generates a random {@link Range} object. @@ -14,9 +14,7 @@ import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; * @throws if {@link maxNumber} argument is less than `2`, * is equal to `NaN` or is `infinite`. */ -export const randomRange = ( - maxNumber: number = 1_000, -): Range => { +export function randomRange(maxNumber: number = 1_000): Range { assert( maxNumber > 1, `Max number must be greater than 1, got '${maxNumber}'.`, @@ -38,16 +36,13 @@ export const randomRange = ( endLineNumber, endColumnNumber, ); -}; +} /** * Generates a random {@link Range} object that is different * from the provided one. */ -export const randomRangeNotEqualTo = ( - differentFrom: Range, - maxTries: number = 10, -): Range => { +export function randomRangeNotEqualTo(differentFrom: Range, maxTries: number = 10): Range { let retriesLeft = maxTries; while (retriesLeft-- > 0) { @@ -60,4 +55,4 @@ export const randomRangeNotEqualTo = ( throw new Error( `Failed to generate a random range different from '${differentFrom}' in ${maxTries} tries.`, ); -}; +} diff --git a/src/vs/editor/test/common/codecs/testUtils/randomTokens.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomTokens.ts similarity index 78% rename from src/vs/editor/test/common/codecs/testUtils/randomTokens.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomTokens.ts index 3e2e2e58734..dedd2340c5e 100644 --- a/src/vs/editor/test/common/codecs/testUtils/randomTokens.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomTokens.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { Text } from '../../../../common/codecs/textToken.js'; -import { randomInt } from '../../../../../base/common/numbers.js'; -import { assertNever } from '../../../../../base/common/assert.js'; -import { NewLine } from '../../../../common/codecs/linesCodec/tokens/newLine.js'; -import { Space, Word } from '../../../../common/codecs/simpleCodec/tokens/index.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { Text } from '../../../../../../common/promptSyntax/codecs/base/textToken.js'; +import { randomInt } from '../../../../../../../../../base/common/numbers.js'; +import { assertNever } from '../../../../../../../../../base/common/assert.js'; +import { NewLine } from '../../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; +import { Space, Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; /** * Token type for the {@link cloneTokens} and {@link randomTokens} functions. @@ -19,9 +19,7 @@ type TToken = NewLine | Space | Word | Text; /** * Test utility to clone a list of provided tokens. */ -export const cloneTokens = ( - tokens: TToken[], -): TToken[] => { +export function cloneTokens(tokens: TToken[]): TToken[] { const clonedTokens: TToken[] = []; for (const token of tokens) { @@ -65,16 +63,12 @@ export const cloneTokens = ( } return clonedTokens; -}; +} /** * Test utility to generate a number of random tokens. */ -export const randomTokens = ( - tokenCount: number = randomInt(20, 10), - startLine: number = randomInt(100, 1), - startColumn: number = randomInt(100, 1), -): TToken[] => { +export function randomTokens(tokenCount: number = randomInt(20, 10), startLine: number = randomInt(100, 1), startColumn: number = randomInt(100, 1)): TToken[] { const tokens = []; let tokensLeft = tokenCount; @@ -149,4 +143,4 @@ export const randomTokens = ( } return tokens; -}; +} diff --git a/src/vs/editor/test/common/codecs/tokens/baseToken.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/baseToken.test.ts similarity index 86% rename from src/vs/editor/test/common/codecs/tokens/baseToken.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/baseToken.test.ts index 88daa8ed79a..9d5975579e0 100644 --- a/src/vs/editor/test/common/codecs/tokens/baseToken.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/baseToken.test.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { randomInt } from '../../../../../base/common/numbers.js'; -import { BaseToken } from '../../../../common/codecs/baseToken.js'; -import { assertDefined } from '../../../../../base/common/types.js'; -import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; -import { NewLine } from '../../../../common/codecs/linesCodec/tokens/newLine.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { randomInt } from '../../../../../../../../../base/common/numbers.js'; +import { BaseToken } from '../../../../../../common/promptSyntax/codecs/base/baseToken.js'; +import { assertDefined } from '../../../../../../../../../base/common/types.js'; +import { randomBoolean } from '../../../../../../../../../base/test/common/testUtils.js'; +import { NewLine } from '../../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; import { randomRange, randomRangeNotEqualTo } from '../testUtils/randomRange.js'; -import { CarriageReturn } from '../../../../common/codecs/linesCodec/tokens/carriageReturn.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { TSimpleToken, WELL_KNOWN_TOKENS } from '../../../../common/codecs/simpleCodec/simpleDecoder.js'; -import { ISimpleTokenClass, SimpleToken } from '../../../../common/codecs/simpleCodec/tokens/simpleToken.js'; -import { At, Colon, DollarSign, ExclamationMark, Hash, LeftAngleBracket, LeftBracket, LeftCurlyBrace, RightAngleBracket, RightBracket, RightCurlyBrace, Slash, Space, Word } from '../../../../common/codecs/simpleCodec/tokens/index.js'; +import { CarriageReturn } from '../../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { TSimpleToken, WELL_KNOWN_TOKENS } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.js'; +import { ISimpleTokenClass, SimpleToken } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.js'; +import { At, Colon, DollarSign, ExclamationMark, Hash, LeftAngleBracket, LeftBracket, LeftCurlyBrace, RightAngleBracket, RightBracket, RightCurlyBrace, Slash, Space, Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; /** * List of simple tokens to randomly select from @@ -30,7 +30,7 @@ const TOKENS: readonly ISimpleTokenClass[] = Object.freeze([ /** * Generates a random {@link SimpleToken} instance. */ -const randomSimpleToken = (): TSimpleToken => { +function randomSimpleToken(): TSimpleToken { const index = randomInt(TOKENS.length - 1); const Constructor = TOKENS[index]; @@ -40,17 +40,17 @@ const randomSimpleToken = (): TSimpleToken => { ); return new Constructor(randomRange()); -}; +} suite('BaseToken', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• render()', () => { + suite('render()', () => { /** * Note! Range of tokens is ignored by the render method, that's * why we generate random ranges for each token in this test. */ - test('• a list of tokens', () => { + test('a list of tokens', () => { const tests: readonly [string, BaseToken[]][] = [ ['/textoftheword$#', [ new Slash(randomRange()), @@ -95,7 +95,7 @@ suite('BaseToken', () => { } }); - test('• accepts tokens delimiter', () => { + test('accepts tokens delimiter', () => { // couple of different delimiters to try const delimiter = (randomBoolean()) ? ', ' @@ -128,7 +128,7 @@ suite('BaseToken', () => { } }); - test('• an empty list of tokens', () => { + test('an empty list of tokens', () => { assert.strictEqual( '', BaseToken.render([]), @@ -137,15 +137,15 @@ suite('BaseToken', () => { }); }); - suite('• fullRange()', () => { - suite('• throws', () => { - test('• if empty list provided', () => { + suite('fullRange()', () => { + suite('throws', () => { + test('if empty list provided', () => { assert.throws(() => { BaseToken.fullRange([]); }); }); - test('• if start line number of the first token is greater than one of the last token', () => { + test('if start line number of the first token is greater than one of the last token', () => { assert.throws(() => { const lastToken = randomSimpleToken(); @@ -177,7 +177,7 @@ suite('BaseToken', () => { }); }); - test('• if start line numbers are equal and end of the first token is greater than the start of the last token', () => { + test('if start line numbers are equal and end of the first token is greater than the start of the last token', () => { assert.throws(() => { const firstToken = randomSimpleToken(); @@ -207,8 +207,8 @@ suite('BaseToken', () => { }); }); - suite('• withRange()', () => { - test('• updates token range', () => { + suite('withRange()', () => { + test('updates token range', () => { class TestToken extends BaseToken { public override get text(): string { throw new Error('Method not implemented.'); @@ -236,8 +236,8 @@ suite('BaseToken', () => { }); }); - suite('• collapseRangeToStart()', () => { - test('• collapses token range to the start position', () => { + suite('collapseRangeToStart()', () => { + test('collapses token range to the start position', () => { class TestToken extends BaseToken { public override get text(): string { throw new Error('Method not implemented.'); @@ -296,8 +296,8 @@ suite('BaseToken', () => { }); }); - suite('• equals()', () => { - test('• true', () => { + suite('equals()', () => { + test('true', () => { class TestToken extends BaseToken { constructor( range: Range, @@ -338,9 +338,9 @@ suite('BaseToken', () => { ); }); - suite('• false', () => { - suite('• different constructor', () => { - test('• same base class', () => { + suite('false', () => { + suite('different constructor', () => { + test('same base class', () => { class TestToken1 extends BaseToken { public override get text(): string { throw new Error('Method not implemented.'); @@ -378,7 +378,7 @@ suite('BaseToken', () => { ); }); - test('• child', () => { + test('child', () => { class TestToken1 extends BaseToken { public override get text(): string { throw new Error('Method not implemented.'); @@ -408,7 +408,7 @@ suite('BaseToken', () => { ); }); - test('• different direct ancestor', () => { + test('different direct ancestor', () => { class TestToken1 extends BaseToken { public override get text(): string { throw new Error('Method not implemented.'); @@ -449,7 +449,7 @@ suite('BaseToken', () => { }); }); - test('• different text', () => { + test('different text', () => { class TestToken extends BaseToken { constructor( private readonly value: string, @@ -482,7 +482,7 @@ suite('BaseToken', () => { ); }); - test('• different range', () => { + test('different range', () => { class TestToken extends BaseToken { public override get text(): string { return 'some text value'; diff --git a/src/vs/editor/test/common/codecs/tokens/compositeToken.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/compositeToken.test.ts similarity index 81% rename from src/vs/editor/test/common/codecs/tokens/compositeToken.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/compositeToken.test.ts index abcdaf1297b..1979deff707 100644 --- a/src/vs/editor/test/common/codecs/tokens/compositeToken.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/compositeToken.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; import { randomRange } from '../testUtils/randomRange.js'; -import { randomInt } from '../../../../../base/common/numbers.js'; -import { BaseToken } from '../../../../common/codecs/baseToken.js'; +import { randomInt } from '../../../../../../../../../base/common/numbers.js'; +import { BaseToken } from '../../../../../../common/promptSyntax/codecs/base/baseToken.js'; import { cloneTokens, randomTokens } from '../testUtils/randomTokens.js'; -import { CompositeToken } from '../../../../common/codecs/compositeToken.js'; -import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; -import { Word } from '../../../../common/codecs/simpleCodec/tokens/index.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { CompositeToken } from '../../../../../../common/promptSyntax/codecs/base/compositeToken.js'; +import { randomBoolean } from '../../../../../../../../../base/test/common/testUtils.js'; +import { Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; suite('CompositeToken', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -32,9 +32,9 @@ suite('CompositeToken', () => { } } - suite('• constructor', () => { - suite('• infers range from the list of tokens', () => { - test('• one token', () => { + suite('constructor', () => { + suite('infers range from the list of tokens', () => { + test('one token', () => { const range = randomRange(); const token = new TestCompositeToken([ new Word( @@ -49,7 +49,7 @@ suite('CompositeToken', () => { ); }); - test('• multiple tokens', () => { + test('multiple tokens', () => { const tokens = randomTokens(); const token = new TestCompositeToken(tokens); @@ -64,21 +64,21 @@ suite('CompositeToken', () => { ); }); - test('• throws if no tokens provided', () => { + test('throws if no tokens provided', () => { assert.throws(() => { new TestCompositeToken([]); }); }); }); - test('• throws if no tokens provided', () => { + test('throws if no tokens provided', () => { assert.throws(() => { new TestCompositeToken([]); }); }); }); - test('• text', () => { + test('text', () => { const tokens = randomTokens(); const token = new TestCompositeToken(tokens); @@ -89,7 +89,7 @@ suite('CompositeToken', () => { ); }); - test('• tokens', () => { + test('tokens', () => { const tokens = randomTokens(); const token = new TestCompositeToken(tokens); @@ -101,9 +101,9 @@ suite('CompositeToken', () => { } }); - suite('• equals', () => { - suite('• true', () => { - test('• same child tokens', () => { + suite('equals', () => { + suite('true', () => { + test('same child tokens', () => { const tokens = randomTokens(); const token1 = new TestCompositeToken(tokens); const token2 = new TestCompositeToken(tokens); @@ -114,7 +114,7 @@ suite('CompositeToken', () => { ); }); - test('• copied child tokens', () => { + test('copied child tokens', () => { const tokens = randomTokens(); const token1 = new TestCompositeToken([...tokens]); const token2 = new TestCompositeToken([...tokens]); @@ -125,7 +125,7 @@ suite('CompositeToken', () => { ); }); - test('• cloned child tokens', () => { + test('cloned child tokens', () => { const tokens = randomTokens(); const tokens1 = cloneTokens(tokens); @@ -140,7 +140,7 @@ suite('CompositeToken', () => { ); }); - test('• composite tokens', () => { + test('composite tokens', () => { const tokens = randomTokens(); // ensure there is at least one composite token @@ -162,8 +162,8 @@ suite('CompositeToken', () => { }); }); - suite('• false', () => { - test('• unknown children number', () => { + suite('false', () => { + test('unknown children number', () => { const token1 = new TestCompositeToken(randomTokens()); const token2 = new TestCompositeToken(randomTokens()); @@ -173,7 +173,7 @@ suite('CompositeToken', () => { ); }); - test('• different number of children', () => { + test('different number of children', () => { const tokens1 = randomTokens(); const tokens2 = randomTokens(); @@ -192,7 +192,7 @@ suite('CompositeToken', () => { ); }); - test('• same number of children', () => { + test('same number of children', () => { const tokensCount = randomInt(20, 10); const tokens1 = randomTokens(tokensCount); @@ -213,7 +213,7 @@ suite('CompositeToken', () => { ); }); - test('• unequal composite tokens', () => { + test('unequal composite tokens', () => { const tokens = randomTokens(); // ensure there is at least one composite token diff --git a/src/vs/editor/test/common/codecs/tokens/simpleToken.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/simpleToken.test.ts similarity index 74% rename from src/vs/editor/test/common/codecs/tokens/simpleToken.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/simpleToken.test.ts index b93db317d87..df349f08cc4 100644 --- a/src/vs/editor/test/common/codecs/tokens/simpleToken.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/simpleToken.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Range } from '../../../../common/core/range.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { SpacingToken, SimpleToken, Space, Tab, VerticalTab } from '../../../../common/codecs/simpleCodec/tokens/index.js'; +import { Range } from '../../../../../../../../../editor/common/core/range.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { SpacingToken, SimpleToken, Space, Tab, VerticalTab } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; suite('SimpleToken', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• SpacingToken', () => { - test('• extends \'SimpleToken\'', () => { + suite('SpacingToken', () => { + test('extends \'SimpleToken\'', () => { class TestClass extends SpacingToken { public override get text(): string { throw new Error('Method not implemented.'); @@ -31,8 +31,8 @@ suite('SimpleToken', () => { }); }); - suite('• Space', () => { - test('• extends \'SpacingToken\'', () => { + suite('Space', () => { + test('extends \'SpacingToken\'', () => { const token = new Space(new Range(1, 1, 1, 2)); assert( @@ -42,8 +42,8 @@ suite('SimpleToken', () => { }); }); - suite('• Tab', () => { - test('• extends \'SpacingToken\'', () => { + suite('Tab', () => { + test('extends \'SpacingToken\'', () => { const token = new Tab(new Range(1, 1, 1, 2)); assert( @@ -53,8 +53,8 @@ suite('SimpleToken', () => { }); }); - suite('• VerticalTab', () => { - test('• extends \'SpacingToken\'', () => { + suite('VerticalTab', () => { + test('extends \'SpacingToken\'', () => { const token = new VerticalTab(new Range(1, 1, 1, 2)); assert( diff --git a/src/vs/editor/test/common/codecs/utils/objectStream.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/objectStream.test.ts similarity index 75% rename from src/vs/editor/test/common/codecs/utils/objectStream.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/objectStream.test.ts index 0dd02446bbc..8ae36566247 100644 --- a/src/vs/editor/test/common/codecs/utils/objectStream.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/objectStream.test.ts @@ -4,23 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { URI } from '../../../../../base/common/uri.js'; -import { createTextModel } from '../../testTextModel.js'; +import { URI } from '../../../../../../../../../base/common/uri.js'; +import { createTextModel } from '../../../../../../../../../editor/test/common/testTextModel.js'; import { randomTokens } from '../testUtils/randomTokens.js'; -import { randomInt } from '../../../../../base/common/numbers.js'; -import { BaseToken } from '../../../../common/codecs/baseToken.js'; -import { assertDefined } from '../../../../../base/common/types.js'; -import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; -import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; -import { arrayToGenerator, ObjectStream } from '../../../../common/codecs/utils/objectStream.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { objectStreamFromTextModel } from '../../../../common/codecs/utils/objectStreamFromTextModel.js'; +import { randomInt } from '../../../../../../../../../base/common/numbers.js'; +import { assertDefined } from '../../../../../../../../../base/common/types.js'; +import { randomBoolean } from '../../../../../../../../../base/test/common/testUtils.js'; +import { CancellationTokenSource } from '../../../../../../../../../base/common/cancellation.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; +import { arrayToGenerator, ObjectStream } from '../../../../../../common/promptSyntax/codecs/base/utils/objectStream.js'; +import { objectStreamFromTextModel } from '../../../../../../common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.js'; +import { BaseToken } from '../../../../../../common/promptSyntax/codecs/base/baseToken.js'; suite('ObjectStream', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); - suite('• fromArray()', () => { - test('• sends objects in the array', async () => { + suite('fromArray()', () => { + test('sends objects in the array', async () => { const tokens = randomTokens(); const stream = disposables.add(ObjectStream.fromArray(tokens)); @@ -30,8 +30,8 @@ suite('ObjectStream', () => { }); }); - suite('• fromTextModel()', () => { - test('• sends data in text model', async () => { + suite('fromTextModel()', () => { + test('sends data in text model', async () => { const initialContents = [ 'some contents', 'with some line breaks', @@ -62,8 +62,8 @@ suite('ObjectStream', () => { }); }); - suite('• cancellation token', () => { - test('• can be cancelled', async () => { + suite('cancellation token', () => { + test('can be cancelled', async () => { const initialContents = [ 'some contents', 'with some line breaks', @@ -121,9 +121,9 @@ suite('ObjectStream', () => { }); }); - suite('• helpers', () => { - suite('• arrayToGenerator()', () => { - test('• sends tokens in the array', async () => { + suite('helpers', () => { + suite('arrayToGenerator()', () => { + test('sends tokens in the array', async () => { const tokens = randomTokens(); const generator = arrayToGenerator(tokens); @@ -141,10 +141,10 @@ suite('ObjectStream', () => { /** * Asserts that two tokens lists are equal. */ -const assertTokensEqual = ( +function assertTokensEqual( receivedTokens: BaseToken[], expectedTokens: BaseToken[], -): void => { +): void { for (let i = 0; i < expectedTokens.length; i++) { const receivedToken = receivedTokens[i]; @@ -158,12 +158,12 @@ const assertTokensEqual = ( `Expected token #${i} to be '${expectedTokens[i]}', got '${receivedToken}'.`, ); } -}; +} /** * Consume a provided stream and return a list of received data objects. */ -const consume = (stream: ObjectStream): Promise => { +function consume(stream: ObjectStream): Promise { return new Promise((resolve, reject) => { const receivedData: T[] = []; stream.on('data', (token) => { @@ -177,4 +177,4 @@ const consume = (stream: ObjectStream): Promise => { reject(error); }); }); -}; +} diff --git a/src/vs/editor/test/common/utils/testDecoder.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/testDecoder.ts similarity index 91% rename from src/vs/editor/test/common/utils/testDecoder.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/testDecoder.ts index 637bf86f6fd..62a1293ff13 100644 --- a/src/vs/editor/test/common/utils/testDecoder.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/testDecoder.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { randomInt } from '../../../../base/common/numbers.js'; -import { BaseToken } from '../../../common/codecs/baseToken.js'; -import { assertDefined } from '../../../../base/common/types.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { WriteableStream } from '../../../../base/common/stream.js'; -import { BaseDecoder } from '../../../../base/common/codecs/baseDecoder.js'; -import { SimpleToken } from '../../../common/codecs/simpleCodec/tokens/simpleToken.js'; +import { VSBuffer } from '../../../../../../../../../base/common/buffer.js'; +import { randomInt } from '../../../../../../../../../base/common/numbers.js'; +import { assertDefined } from '../../../../../../../../../base/common/types.js'; +import { Disposable } from '../../../../../../../../../base/common/lifecycle.js'; +import { WriteableStream } from '../../../../../../../../../base/common/stream.js'; +import { BaseDecoder } from '../../../../../../common/promptSyntax/codecs/base/baseDecoder.js'; +import { BaseToken } from '../../../../../../common/promptSyntax/codecs/base/baseToken.js'; +import { SimpleToken } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.js'; + /** * Kind of decoder tokens consume methods are different ways diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptCodec.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptCodec.test.ts index d8e551d766b..8c5b9ded809 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptCodec.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptCodec.test.ts @@ -6,10 +6,10 @@ import { VSBuffer } from '../../../../../../../base/common/buffer.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; import { newWriteableStream } from '../../../../../../../base/common/stream.js'; -import { TestDecoder } from '../../../../../../../editor/test/common/utils/testDecoder.js'; +import { TestDecoder } from './base/utils/testDecoder.js'; import { ChatPromptCodec } from '../../../../common/promptSyntax/codecs/chatPromptCodec.js'; -import { NewLine } from '../../../../../../../editor/common/codecs/linesCodec/tokens/newLine.js'; -import { Space, Tab, Word } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/index.js'; +import { NewLine } from '../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; +import { Space, Tab, Word } from '../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; import { PromptVariableWithData } from '../../../../common/promptSyntax/codecs/tokens/promptVariable.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { ChatPromptDecoder, TChatPromptToken } from '../../../../common/promptSyntax/codecs/chatPromptDecoder.js'; @@ -48,7 +48,7 @@ export class TestChatPromptCodec extends TestDecoder { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); - test('• produces expected tokens', async () => { + test('produces expected tokens', async () => { const test = testDisposables.add(new TestChatPromptCodec()); await test.run( diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptDecoder.test.ts index 893986f2a59..45572f00fc1 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptDecoder.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptDecoder.test.ts @@ -6,16 +6,16 @@ import { VSBuffer } from '../../../../../../../base/common/buffer.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; import { newWriteableStream } from '../../../../../../../base/common/stream.js'; -import { TestDecoder } from '../../../../../../../editor/test/common/utils/testDecoder.js'; -import { NewLine } from '../../../../../../../editor/common/codecs/linesCodec/tokens/newLine.js'; +import { TestDecoder } from './base/utils/testDecoder.js'; +import { NewLine } from '../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; import { PromptAtMention } from '../../../../common/promptSyntax/codecs/tokens/promptAtMention.js'; import { PromptSlashCommand } from '../../../../common/promptSyntax/codecs/tokens/promptSlashCommand.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; -import { MarkdownLink } from '../../../../../../../editor/common/codecs/markdownCodec/tokens/markdownLink.js'; +import { MarkdownLink } from '../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.js'; import { PromptTemplateVariable } from '../../../../common/promptSyntax/codecs/tokens/promptTemplateVariable.js'; import { ChatPromptDecoder, TChatPromptToken } from '../../../../common/promptSyntax/codecs/chatPromptDecoder.js'; import { PromptVariable, PromptVariableWithData } from '../../../../common/promptSyntax/codecs/tokens/promptVariable.js'; -import { At, Dash, ExclamationMark, FormFeed, Hash, Space, Tab, VerticalTab, Word } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/index.js'; +import { At, Dash, ExclamationMark, FormFeed, Hash, Space, Tab, VerticalTab, Word } from '../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; /** * A reusable test utility that asserts that a `ChatPromptDecoder` instance @@ -52,7 +52,7 @@ export class TestChatPromptDecoder extends TestDecoder { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); - test('• produces expected tokens', async () => { + test('produces expected tokens', async () => { const test = testDisposables.add( new TestChatPromptDecoder(), ); @@ -205,8 +205,8 @@ suite('ChatPromptDecoder', () => { ); }); - suite('• variables', () => { - test('• produces expected tokens', async () => { + suite('variables', () => { + test('produces expected tokens', async () => { const test = testDisposables.add( new TestChatPromptDecoder(), ); @@ -263,8 +263,8 @@ suite('ChatPromptDecoder', () => { }); }); - suite('• commands', () => { - test('• produces expected tokens', async () => { + suite('commands', () => { + test('produces expected tokens', async () => { const test = testDisposables.add( new TestChatPromptDecoder(), ); @@ -350,8 +350,8 @@ suite('ChatPromptDecoder', () => { }); }); - suite('• template variables', () => { - test('• produces expected tokens', async () => { + suite('template variables', () => { + test('produces expected tokens', async () => { const test = testDisposables.add( new TestChatPromptDecoder(), ); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/markdownExtensionsDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/markdownExtensionsDecoder.test.ts index 51955d8ec64..d673169ba4d 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/markdownExtensionsDecoder.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/markdownExtensionsDecoder.test.ts @@ -7,20 +7,20 @@ import { assert } from '../../../../../../../base/common/assert.js'; import { VSBuffer } from '../../../../../../../base/common/buffer.js'; import { randomInt } from '../../../../../../../base/common/numbers.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { Text } from '../../../../../../../editor/common/codecs/textToken.js'; +import { Text } from '../../../../common/promptSyntax/codecs/base/textToken.js'; import { newWriteableStream } from '../../../../../../../base/common/stream.js'; import { randomBoolean } from '../../../../../../../base/test/common/testUtils.js'; -import { TestDecoder } from '../../../../../../../editor/test/common/utils/testDecoder.js'; -import { Word } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/word.js'; -import { NewLine } from '../../../../../../../editor/common/codecs/linesCodec/tokens/newLine.js'; +import { TestDecoder } from './base/utils/testDecoder.js'; +import { Word } from '../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/word.js'; +import { NewLine } from '../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; import { type TChatPromptToken } from '../../../../common/promptSyntax/codecs/chatPromptDecoder.js'; -import { TestSimpleDecoder } from '../../../../../../../editor/test/common/codecs/simpleDecoder.test.js'; +import { TestSimpleDecoder } from './base/simpleDecoder.test.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; -import { CarriageReturn } from '../../../../../../../editor/common/codecs/linesCodec/tokens/carriageReturn.js'; -import { FrontMatterHeader } from '../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js'; -import { Colon, Dash, DoubleQuote, Space, Tab, VerticalTab } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/index.js'; -import { MarkdownExtensionsDecoder } from '../../../../../../../editor/common/codecs/markdownExtensionsCodec/markdownExtensionsDecoder.js'; -import { FrontMatterMarker, TMarkerToken } from '../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterMarker.js'; +import { CarriageReturn } from '../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; +import { FrontMatterHeader } from '../../../../common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; +import { Colon, Dash, DoubleQuote, Space, Tab, VerticalTab } from '../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; +import { MarkdownExtensionsDecoder } from '../../../../common/promptSyntax/codecs/base/markdownExtensionsCodec/markdownExtensionsDecoder.js'; +import { FrontMatterMarker, TMarkerToken } from '../../../../common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.js'; /** * Type for supported end-of-line tokens. @@ -148,9 +148,9 @@ suite('MarkdownExtensionsDecoder', () => { return new Array(dashCount).fill('-').join(''); }; - suite('• Front Matter header', () => { - suite('• successful cases', () => { - test('• produces expected tokens', async () => { + suite('Front Matter header', () => { + suite('successful cases', () => { + test('produces expected tokens', async () => { const test = disposables.add( new TestMarkdownExtensionsDecoder(), ); @@ -206,7 +206,7 @@ suite('MarkdownExtensionsDecoder', () => { ); }); - test('• can contain dashes in the header contents', async () => { + test('can contain dashes in the header contents', async () => { const test = disposables.add( new TestMarkdownExtensionsDecoder(), ); @@ -276,7 +276,7 @@ suite('MarkdownExtensionsDecoder', () => { ); }); - test('• can be at the end of the file', async () => { + test('can be at the end of the file', async () => { const test = disposables.add( new TestMarkdownExtensionsDecoder(), ); @@ -326,8 +326,8 @@ suite('MarkdownExtensionsDecoder', () => { }); }); - suite('• failure cases', () => { - test('• fails if header starts not on the first line', async () => { + suite('failure cases', () => { + test('fails if header starts not on the first line', async () => { const test = disposables.add( new TestMarkdownExtensionsDecoder(), ); @@ -366,7 +366,7 @@ suite('MarkdownExtensionsDecoder', () => { ); }); - test('• fails if header markers do not match (start marker is longer)', async () => { + test('fails if header markers do not match (start marker is longer)', async () => { const test = disposables.add( new TestMarkdownExtensionsDecoder(), ); @@ -404,7 +404,7 @@ suite('MarkdownExtensionsDecoder', () => { ); }); - test('• fails if header markers do not match (end marker is longer)', async () => { + test('fails if header markers do not match (end marker is longer)', async () => { const test = disposables.add( new TestMarkdownExtensionsDecoder(), ); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/fileReference.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/fileReference.test.ts index 3478fd7d34d..de38092ac21 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/fileReference.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/fileReference.test.ts @@ -7,7 +7,7 @@ import assert from 'assert'; import { randomInt } from '../../../../../../../../base/common/numbers.js'; import { Range } from '../../../../../../../../editor/common/core/range.js'; import { assertDefined } from '../../../../../../../../base/common/types.js'; -import { BaseToken } from '../../../../../../../../editor/common/codecs/baseToken.js'; +import { BaseToken } from '../../../../../common/promptSyntax/codecs/base/baseToken.js'; import { PromptToken } from '../../../../../common/promptSyntax/codecs/tokens/promptToken.js'; import { FileReference } from '../../../../../common/promptSyntax/codecs/tokens/fileReference.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; @@ -16,7 +16,7 @@ import { PromptVariable, PromptVariableWithData } from '../../../../../common/pr suite('FileReference', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('• linkRange', () => { + test('linkRange', () => { const lineNumber = randomInt(100, 1); const columnStartNumber = randomInt(100, 1); const path = `/temp/test/file-${randomInt(Number.MAX_SAFE_INTEGER)}.txt`; @@ -48,7 +48,7 @@ suite('FileReference', () => { ); }); - test('• path', () => { + test('path', () => { const lineNumber = randomInt(100, 1); const columnStartNumber = randomInt(100, 1); const link = `/temp/test/file-${randomInt(Number.MAX_SAFE_INTEGER)}.txt`; @@ -69,7 +69,7 @@ suite('FileReference', () => { ); }); - test('• extends `PromptVariableWithData` and others', () => { + test('extends `PromptVariableWithData` and others', () => { const lineNumber = randomInt(100, 1); const columnStartNumber = randomInt(100, 1); const link = `/temp/test/file-${randomInt(Number.MAX_SAFE_INTEGER)}.txt`; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/markdownLink.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/markdownLink.test.ts index d012f30e686..ecbf48a1fa4 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/markdownLink.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/markdownLink.test.ts @@ -7,10 +7,10 @@ import assert from 'assert'; import { randomInt } from '../../../../../../../../base/common/numbers.js'; import { Range } from '../../../../../../../../editor/common/core/range.js'; import { assertDefined } from '../../../../../../../../base/common/types.js'; -import { BaseToken } from '../../../../../../../../editor/common/codecs/baseToken.js'; +import { BaseToken } from '../../../../../common/promptSyntax/codecs/base/baseToken.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; -import { MarkdownLink } from '../../../../../../../../editor/common/codecs/markdownCodec/tokens/markdownLink.js'; -import { MarkdownToken } from '../../../../../../../../editor/common/codecs/markdownCodec/tokens/markdownToken.js'; +import { MarkdownLink } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.js'; +import { MarkdownToken } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownToken.js'; suite('FileReference', () => { ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/src/vs/platform/prompts/test/common/config.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts similarity index 91% rename from src/vs/platform/prompts/test/common/config.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts index eecb8427a9c..2ac76531de2 100644 --- a/src/vs/platform/prompts/test/common/config.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { mockService } from './utils/mock.js'; -import { PromptsConfig } from '../../common/config.js'; -import { randomInt } from '../../../../base/common/numbers.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; -import { IConfigurationOverrides, IConfigurationService } from '../../../configuration/common/configuration.js'; -import { PromptsType } from '../../common/prompts.js'; +import { mockService } from '../utils/mock.js'; +import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; +import { PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; +import { randomInt } from '../../../../../../../base/common/numbers.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { IConfigurationOverrides, IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; /** * Mocked instance of {@link IConfigurationService}. */ -const createMock = (value: T): IConfigurationService => { +function createMock(value: T): IConfigurationService { return mockService({ getValue(key?: string | IConfigurationOverrides) { assert( @@ -30,13 +30,13 @@ const createMock = (value: T): IConfigurationService => { return value; }, }); -}; +} suite('PromptsConfig', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• enabled', () => { - test('• true', () => { + suite('enabled', () => { + test('true', () => { const configService = createMock(true); assert.strictEqual( @@ -46,7 +46,7 @@ suite('PromptsConfig', () => { ); }); - test('• false', () => { + test('false', () => { const configService = createMock(false); assert.strictEqual( @@ -56,7 +56,7 @@ suite('PromptsConfig', () => { ); }); - test('• null', () => { + test('null', () => { const configService = createMock(null); assert.strictEqual( @@ -66,7 +66,7 @@ suite('PromptsConfig', () => { ); }); - test('• string', () => { + test('string', () => { const configService = createMock(''); assert.strictEqual( @@ -76,7 +76,7 @@ suite('PromptsConfig', () => { ); }); - test('• true string', () => { + test('true string', () => { const configService = createMock('TRUE'); assert.strictEqual( @@ -86,7 +86,7 @@ suite('PromptsConfig', () => { ); }); - test('• false string', () => { + test('false string', () => { const configService = createMock('FaLsE'); assert.strictEqual( @@ -96,7 +96,7 @@ suite('PromptsConfig', () => { ); }); - test('• number', () => { + test('number', () => { const configService = createMock(randomInt(100)); assert.strictEqual( @@ -106,7 +106,7 @@ suite('PromptsConfig', () => { ); }); - test('• NaN', () => { + test('NaN', () => { const configService = createMock(NaN); assert.strictEqual( @@ -116,7 +116,7 @@ suite('PromptsConfig', () => { ); }); - test('• bigint', () => { + test('bigint', () => { const configService = createMock(BigInt(randomInt(100))); assert.strictEqual( @@ -126,7 +126,7 @@ suite('PromptsConfig', () => { ); }); - test('• symbol', () => { + test('symbol', () => { const configService = createMock(Symbol('test')); assert.strictEqual( @@ -136,7 +136,7 @@ suite('PromptsConfig', () => { ); }); - test('• object', () => { + test('object', () => { const configService = createMock({ '.github/prompts': false, }); @@ -148,7 +148,7 @@ suite('PromptsConfig', () => { ); }); - test('• array', () => { + test('array', () => { const configService = createMock(['.github/prompts']); assert.strictEqual( @@ -160,8 +160,8 @@ suite('PromptsConfig', () => { }); - suite('• getLocationsValue', () => { - test('• undefined', () => { + suite('getLocationsValue', () => { + test('undefined', () => { const configService = createMock(undefined); assert.strictEqual( @@ -171,7 +171,7 @@ suite('PromptsConfig', () => { ); }); - test('• null', () => { + test('null', () => { const configService = createMock(null); assert.strictEqual( @@ -181,8 +181,8 @@ suite('PromptsConfig', () => { ); }); - suite('• object', () => { - test('• empty', () => { + suite('object', () => { + test('empty', () => { assert.deepStrictEqual( PromptsConfig.getLocationsValue(createMock({}), PromptsType.prompt), {}, @@ -190,7 +190,7 @@ suite('PromptsConfig', () => { ); }); - test('• only valid strings', () => { + test('only valid strings', () => { assert.deepStrictEqual( PromptsConfig.getLocationsValue(createMock({ '/root/.bashrc': true, @@ -224,7 +224,7 @@ suite('PromptsConfig', () => { ); }); - test('• filters out non valid entries', () => { + test('filters out non valid entries', () => { assert.deepStrictEqual( PromptsConfig.getLocationsValue(createMock({ '/etc/hosts.backup': '\t\n\t', @@ -263,7 +263,7 @@ suite('PromptsConfig', () => { ); }); - test('• only invalid or false values', () => { + test('only invalid or false values', () => { assert.deepStrictEqual( PromptsConfig.getLocationsValue(createMock({ '/etc/hosts.backup': '\t\n\t', @@ -286,8 +286,8 @@ suite('PromptsConfig', () => { }); }); - suite('• sourceLocations', () => { - test('• undefined', () => { + suite('sourceLocations', () => { + test('undefined', () => { const configService = createMock(undefined); assert.deepStrictEqual( @@ -297,7 +297,7 @@ suite('PromptsConfig', () => { ); }); - test('• null', () => { + test('null', () => { const configService = createMock(null); assert.deepStrictEqual( diff --git a/src/vs/platform/prompts/test/common/constants.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/constants.test.ts similarity index 81% rename from src/vs/platform/prompts/test/common/constants.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/config/constants.test.ts index d22d8f283dc..cbde965cf2d 100644 --- a/src/vs/platform/prompts/test/common/constants.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/constants.test.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { URI } from '../../../../base/common/uri.js'; -import { randomInt } from '../../../../base/common/numbers.js'; -import { getCleanPromptName, isPromptOrInstructionsFile } from '../../common/prompts.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { getCleanPromptName, isPromptOrInstructionsFile } from '../../../../common/promptSyntax/config/promptFileLocations.js'; +import { randomInt } from '../../../../../../../base/common/numbers.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { URI } from '../../../../../../../base/common/uri.js'; suite('Prompt Constants', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• getCleanPromptName', () => { - test('• returns a clean prompt name', () => { + suite('getCleanPromptName', () => { + test('returns a clean prompt name', () => { assert.strictEqual( getCleanPromptName(URI.file('/path/to/my-prompt.prompt.md')), 'my-prompt', @@ -53,8 +53,8 @@ suite('Prompt Constants', () => { }); }); - suite('• isPromptOrInstructionsFile', () => { - test('• returns `true` for prompt files', () => { + suite('isPromptOrInstructionsFile', () => { + test('returns `true` for prompt files', () => { assert( isPromptOrInstructionsFile(URI.file('/path/to/my-prompt.prompt.md')), ); @@ -72,7 +72,7 @@ suite('Prompt Constants', () => { ); }); - test('• returns `false` for non-prompt files', () => { + test('returns `false` for non-prompt files', () => { assert( !isPromptOrInstructionsFile(URI.file('/path/to/my-prompt.prompt.md1')), ); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/contentProviders/filePromptContentsProvider.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/contentProviders/filePromptContentsProvider.test.ts index 8600c57af5b..cf0091333a7 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/contentProviders/filePromptContentsProvider.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/contentProviders/filePromptContentsProvider.test.ts @@ -13,11 +13,12 @@ import { ReadableStream } from '../../../../../../../base/common/stream.js'; import { NotPromptFile } from '../../../../common/promptFileReferenceErrors.js'; import { IFileService } from '../../../../../../../platform/files/common/files.js'; import { FileService } from '../../../../../../../platform/files/common/fileService.js'; -import { randomBoolean, wait } from '../../../../../../../base/test/common/testUtils.js'; +import { randomBoolean } from '../../../../../../../base/test/common/testUtils.js'; +import { timeout } from '../../../../../../../base/common/async.js'; import { NullPolicyService } from '../../../../../../../platform/policy/common/policy.js'; -import { Line } from '../../../../../../../editor/common/codecs/linesCodec/tokens/line.js'; +import { Line } from '../../../../common/promptSyntax/codecs/base/linesCodec/tokens/line.js'; import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; -import { LinesDecoder } from '../../../../../../../editor/common/codecs/linesCodec/linesDecoder.js'; +import { LinesDecoder } from '../../../../common/promptSyntax/codecs/base/linesCodec/linesDecoder.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; import { ConfigurationService } from '../../../../../../../platform/configuration/common/configurationService.js'; @@ -54,7 +55,7 @@ suite('FilePromptContentsProvider', () => { instantiationService.stub(IConfigurationService, nullConfigService); }); - test('• provides contents of a file', async () => { + test('provides contents of a file', async () => { const fileService = instantiationService.get(IFileService); const fileName = `file-${randomInt(10000)}.prompt.md`; @@ -64,7 +65,7 @@ suite('FilePromptContentsProvider', () => { await fileService.del(fileUri); } await fileService.writeFile(fileUri, VSBuffer.fromString('Hello, world!')); - await wait(5); + await timeout(5); const contentsProvider = testDisposables.add(instantiationService.createInstance( FilePromptContentProvider, @@ -78,7 +79,7 @@ suite('FilePromptContentsProvider', () => { })); contentsProvider.start(); - await wait(CONTENT_CHANGED_TIMEOUT); + await timeout(CONTENT_CHANGED_TIMEOUT); assertDefined( streamOrError, @@ -107,9 +108,9 @@ suite('FilePromptContentsProvider', () => { ); }); - suite('• options', () => { - suite('• allowNonPromptFiles', () => { - test('• true', async () => { + suite('options', () => { + suite('allowNonPromptFiles', () => { + test('true', async () => { const fileService = instantiationService.get(IFileService); const fileName = (randomBoolean() === true) @@ -122,7 +123,7 @@ suite('FilePromptContentsProvider', () => { await fileService.del(fileUri); } await fileService.writeFile(fileUri, VSBuffer.fromString('Hello, world!')); - await wait(5); + await timeout(5); const contentsProvider = testDisposables.add(instantiationService.createInstance( FilePromptContentProvider, @@ -136,7 +137,7 @@ suite('FilePromptContentsProvider', () => { })); contentsProvider.start(); - await wait(CONTENT_CHANGED_TIMEOUT); + await timeout(CONTENT_CHANGED_TIMEOUT); assertDefined( streamOrError, @@ -165,7 +166,7 @@ suite('FilePromptContentsProvider', () => { ); }); - test('• false', async () => { + test('false', async () => { const fileService = instantiationService.get(IFileService); const fileName = (randomBoolean() === true) @@ -178,7 +179,7 @@ suite('FilePromptContentsProvider', () => { await fileService.del(fileUri); } await fileService.writeFile(fileUri, VSBuffer.fromString('Hello, world!')); - await wait(5); + await timeout(5); const contentsProvider = testDisposables.add(instantiationService.createInstance( FilePromptContentProvider, @@ -192,7 +193,7 @@ suite('FilePromptContentsProvider', () => { })); contentsProvider.start(); - await wait(CONTENT_CHANGED_TIMEOUT); + await timeout(CONTENT_CHANGED_TIMEOUT); assertDefined( streamOrError, @@ -205,7 +206,7 @@ suite('FilePromptContentsProvider', () => { ); }); - test('• undefined', async () => { + test('undefined', async () => { const fileService = instantiationService.get(IFileService); const fileName = (randomBoolean() === true) @@ -218,7 +219,7 @@ suite('FilePromptContentsProvider', () => { await fileService.del(fileUri); } await fileService.writeFile(fileUri, VSBuffer.fromString('Hello, world!')); - await wait(5); + await timeout(5); const contentsProvider = testDisposables.add(instantiationService.createInstance( FilePromptContentProvider, @@ -232,7 +233,7 @@ suite('FilePromptContentsProvider', () => { })); contentsProvider.start(); - await wait(CONTENT_CHANGED_TIMEOUT); + await timeout(CONTENT_CHANGED_TIMEOUT); assertDefined( streamOrError, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts index 32783cd5525..cd1aa4c8915 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts @@ -15,14 +15,14 @@ import { Disposable } from '../../../../../../../base/common/lifecycle.js'; import { OpenFailed } from '../../../../common/promptFileReferenceErrors.js'; import { IFileService } from '../../../../../../../platform/files/common/files.js'; import { randomBoolean } from '../../../../../../../base/test/common/testUtils.js'; -import { PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; +import { PROMPT_LANGUAGE_ID, INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; import { FileService } from '../../../../../../../platform/files/common/fileService.js'; import { createTextModel } from '../../../../../../../editor/test/common/testTextModel.js'; import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../../../common/promptSyntax/constants.js'; + import { InMemoryFileSystemProvider } from '../../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; import { ExpectedDiagnosticError, ExpectedDiagnosticWarning, TExpectedDiagnostic } from '../testUtils/expectedDiagnostic.js'; import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; @@ -177,7 +177,7 @@ suite('TextModelPromptParser', () => { ); }; - test('• core logic #1', async () => { + test('core logic #1', async () => { const test = createTest( URI.file('/foo/bar.md'), [ @@ -227,7 +227,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• core logic #2', async () => { + test('core logic #2', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -289,7 +289,7 @@ suite('TextModelPromptParser', () => { ]); }); - suite('• header', () => { + suite('header', () => { suite(' • metadata', () => { suite(' • instructions', () => { test(`• empty header`, async () => { @@ -591,8 +591,8 @@ suite('TextModelPromptParser', () => { }); }); - suite('• diagnostics', () => { - test('• core logic', async () => { + suite('diagnostics', () => { + test('core logic', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -685,8 +685,8 @@ suite('TextModelPromptParser', () => { ]); }); - suite('• tools metadata', () => { - test('• tool names can be quoted and non-quoted string', async () => { + suite('tools metadata', () => { + test('tool names can be quoted and non-quoted string', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -722,9 +722,9 @@ suite('TextModelPromptParser', () => { }); }); - suite('• applyTo metadata', () => { - suite('• language', () => { - test('• prompt', async () => { + suite('applyTo metadata', () => { + suite('language', () => { + test('prompt', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -762,7 +762,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• instructions', async () => { + test('instructions', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -802,7 +802,7 @@ suite('TextModelPromptParser', () => { }); }); - test('• invalid glob pattern', async () => { + test('invalid glob pattern', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -843,9 +843,9 @@ suite('TextModelPromptParser', () => { ]); }); - suite('• mode', () => { - suite('• invalid', () => { - test('• quoted string value', async () => { + suite('mode', () => { + suite('invalid', () => { + test('quoted string value', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -873,7 +873,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• single-token unquoted-string value', async () => { + test('single-token unquoted-string value', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -901,7 +901,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• unquoted string value', async () => { + test('unquoted string value', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -929,7 +929,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• multi-token unquoted-string value', async () => { + test('multi-token unquoted-string value', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -957,7 +957,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• after a description metadata', async () => { + test('after a description metadata', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -986,7 +986,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• boolean value', async () => { + test('boolean value', async () => { const booleanValue = randomBoolean(); const test = createTest( @@ -1016,7 +1016,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• empty quoted string value', async () => { + test('empty quoted string value', async () => { const quotedString = (randomBoolean()) ? `''` : '""'; @@ -1048,7 +1048,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• empty value', async () => { + test('empty value', async () => { const value = (randomBoolean()) ? '\t\t \t\t' : ' \t \v \t '; @@ -1080,7 +1080,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• void value', async () => { + test('void value', async () => { const test = createTest( URI.file('/absolute/folder/and/a/my.prompt.md'), [ @@ -1110,9 +1110,9 @@ suite('TextModelPromptParser', () => { }); }); - suite('• tools and mode compatibility', () => { - suite('• tools is set', () => { - test('• ask mode', async () => { + suite('tools and mode compatibility', () => { + suite('tools is set', () => { + test('ask mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1158,7 +1158,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• edit mode', async () => { + test('edit mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1204,7 +1204,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• agent mode', async () => { + test('agent mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1245,7 +1245,7 @@ suite('TextModelPromptParser', () => { await test.validateHeaderDiagnostics([]); }); - test('• no mode', async () => { + test('no mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1285,7 +1285,7 @@ suite('TextModelPromptParser', () => { await test.validateHeaderDiagnostics([]); }); - test('• invalid mode', async () => { + test('invalid mode', async () => { const value = (randomBoolean()) ? 'unknown mode ' : 'unknown'; @@ -1336,8 +1336,8 @@ suite('TextModelPromptParser', () => { }); }); - suite('• tools is not set', () => { - test('• ask mode', async () => { + suite('tools is not set', () => { + test('ask mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1383,7 +1383,7 @@ suite('TextModelPromptParser', () => { ]); }); - test('• edit mode', async () => { + test('edit mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1424,7 +1424,7 @@ suite('TextModelPromptParser', () => { await test.validateHeaderDiagnostics([]); }); - test('• agent mode', async () => { + test('agent mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1464,7 +1464,7 @@ suite('TextModelPromptParser', () => { await test.validateHeaderDiagnostics([]); }); - test('• no mode', async () => { + test('no mode', async () => { const test = createTest( URI.file('/absolute/folder/and/a/filename.txt'), [ @@ -1506,7 +1506,7 @@ suite('TextModelPromptParser', () => { }); }); - test('• gets disposed with the model', async () => { + test('gets disposed with the model', async () => { const test = createTest( URI.file('/some/path/file.prompt.md'), [ @@ -1527,7 +1527,7 @@ suite('TextModelPromptParser', () => { ); }); - test('• toString()', async () => { + test('toString()', async () => { const modelUri = URI.file('/Users/legomushroom/repos/prompt-snippets/README.md'); const test = createTest( modelUri, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts index f769d9d2aee..6c32ad7c098 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts @@ -4,33 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { ChatMode } from '../../../common/constants.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { Schemas } from '../../../../../../base/common/network.js'; -import { Range } from '../../../../../../editor/common/core/range.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; +import { timeout } from '../../../../../../base/common/async.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { IMockFolder, MockFilesystem } from './testUtils/mockFilesystem.js'; -import { IFileService } from '../../../../../../platform/files/common/files.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { FileService } from '../../../../../../platform/files/common/fileService.js'; -import { type TPromptReference } from '../../../common/promptSyntax/parsers/types.js'; -import { NullPolicyService } from '../../../../../../platform/policy/common/policy.js'; -import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; -import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js'; -import { FileReference } from '../../../common/promptSyntax/codecs/tokens/fileReference.js'; -import { FilePromptParser } from '../../../common/promptSyntax/parsers/filePromptParser.js'; -import { waitRandom, randomBoolean } from '../../../../../../base/test/common/testUtils.js'; -import { getPromptFileType, PromptsType } from '../../../../../../platform/prompts/common/prompts.js'; +import { Schemas } from '../../../../../../base/common/network.js'; +import { assertDefined } from '../../../../../../base/common/types.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { randomBoolean } from '../../../../../../base/test/common/testUtils.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; +import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { MarkdownLink } from '../../../../../../editor/common/codecs/markdownCodec/tokens/markdownLink.js'; import { ConfigurationService } from '../../../../../../platform/configuration/common/configurationService.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { FileService } from '../../../../../../platform/files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; -import { IPromptParserOptions, type TErrorCondition } from '../../../common/promptSyntax/parsers/basePromptParser.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; -import { NotPromptFile, RecursiveReference, OpenFailed, FolderReference } from '../../../common/promptFileReferenceErrors.js'; +import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js'; +import { NullPolicyService } from '../../../../../../platform/policy/common/policy.js'; +import { ChatMode } from '../../../common/constants.js'; +import { FolderReference, NotPromptFile, OpenFailed, RecursiveReference } from '../../../common/promptFileReferenceErrors.js'; +import { MarkdownLink } from '../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.js'; +import { FileReference } from '../../../common/promptSyntax/codecs/tokens/fileReference.js'; +import { getPromptFileType } from '../../../common/promptSyntax/config/promptFileLocations.js'; +import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { IPromptParserOptions, type TErrorCondition } from '../../../common/promptSyntax/parsers/basePromptParser.js'; +import { FilePromptParser } from '../../../common/promptSyntax/parsers/filePromptParser.js'; +import { type TPromptReference } from '../../../common/promptSyntax/parsers/types.js'; +import { IMockFolder, MockFilesystem } from './testUtils/mockFilesystem.js'; /** * Represents a file reference with an expected @@ -97,7 +99,7 @@ class TestPromptFileReference extends Disposable { // randomly test with and without delay to ensure that the file // reference resolution is not susceptible to race conditions if (randomBoolean()) { - await waitRandom(5); + await timeout(5); } // start resolving references for the specified root file @@ -199,11 +201,11 @@ class TestPromptFileReference extends Disposable { * @param lineNumber The expected line number of the file reference. * @param startColumnNumber The expected start column number of the file reference. */ -const createTestFileReference = ( +function createTestFileReference( filePath: string, lineNumber: number, startColumnNumber: number, -): FileReference => { +): FileReference { const range = new Range( lineNumber, startColumnNumber, @@ -212,7 +214,7 @@ const createTestFileReference = ( ); return new FileReference(range, filePath); -}; +} suite('PromptFileReference', function () { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -241,7 +243,7 @@ suite('PromptFileReference', function () { }); }); - test('• resolves nested file references', async function () { + test('resolves nested file references', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -391,7 +393,7 @@ suite('PromptFileReference', function () { await test.run(); }); - test('• does not fall into infinite reference recursion', async function () { + test('does not fall into infinite reference recursion', async function () { const rootFolderName = 'infinite-recursion'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -540,8 +542,8 @@ suite('PromptFileReference', function () { await test.run(); }); - suite('• options', () => { - test('• allowNonPromptFiles', async function () { + suite('options', () => { + test('allowNonPromptFiles', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -692,8 +694,8 @@ suite('PromptFileReference', function () { }); }); - suite('• metadata', () => { - test('• tools', async function () { + suite('metadata', () => { + test('tools', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -904,8 +906,8 @@ suite('PromptFileReference', function () { ); }); - suite('• applyTo', () => { - test('• prompt language', async function () { + suite('applyTo', () => { + test('prompt language', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -1023,7 +1025,7 @@ suite('PromptFileReference', function () { }); - test('• instructions language', async function () { + test('instructions language', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -1135,8 +1137,8 @@ suite('PromptFileReference', function () { }); }); - suite('• tools and mode compatibility', () => { - test('• tools are ignored if root prompt is in the ask mode', async function () { + suite('tools and mode compatibility', () => { + test('tools are ignored if root prompt is in the ask mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -1247,7 +1249,7 @@ suite('PromptFileReference', function () { ); }); - test('• tools are ignored if root prompt is in the edit mode', async function () { + test('tools are ignored if root prompt is in the edit mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -1357,7 +1359,7 @@ suite('PromptFileReference', function () { ); }); - test('• tools are not ignored if root prompt is in the agent mode', async function () { + test('tools are not ignored if root prompt is in the agent mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); @@ -1471,7 +1473,7 @@ suite('PromptFileReference', function () { ); }); - test('• tools are not ignored if root prompt implicitly in the agent mode', async function () { + test('tools are not ignored if root prompt implicitly in the agent mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = URI.file(rootFolder); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 4e8eb7333f6..a3d8385568a 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -5,31 +5,31 @@ import assert from 'assert'; import * as sinon from 'sinon'; -import { URI } from '../../../../../../../base/common/uri.js'; -import { MockFilesystem } from '../testUtils/mockFilesystem.js'; -import { pick } from '../../../../../../../base/common/arrays.js'; +import { timeout } from '../../../../../../../base/common/async.js'; import { Schemas } from '../../../../../../../base/common/network.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; import { assertDefined } from '../../../../../../../base/common/types.js'; -import { IPromptsService } from '../../../../common/promptSyntax/service/types.js'; -import { IFileService } from '../../../../../../../platform/files/common/files.js'; -import { IModelService } from '../../../../../../../editor/common/services/model.js'; -import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; -import { FileService } from '../../../../../../../platform/files/common/fileService.js'; -import { createTextModel } from '../../../../../../../editor/test/common/testTextModel.js'; -import { PromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; -import { ILanguageService } from '../../../../../../../editor/common/languages/language.js'; -import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; -import { randomBoolean, waitRandom } from '../../../../../../../base/test/common/testUtils.js'; -import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { randomBoolean } from '../../../../../../../base/test/common/testUtils.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { Range } from '../../../../../../../editor/common/core/range.js'; +import { ILanguageService } from '../../../../../../../editor/common/languages/language.js'; +import { IModelService } from '../../../../../../../editor/common/services/model.js'; +import { createTextModel } from '../../../../../../../editor/test/common/testTextModel.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; -import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../../../common/promptSyntax/constants.js'; +import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { IFileService } from '../../../../../../../platform/files/common/files.js'; +import { FileService } from '../../../../../../../platform/files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; -import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { INSTRUCTION_FILE_EXTENSION, PROMPT_FILE_EXTENSION, PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; +import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; import { IWorkspacesService } from '../../../../../../../platform/workspaces/common/workspaces.js'; +import { INSTRUCTION_FILE_EXTENSION, PROMPT_FILE_EXTENSION } from '../../../../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; +import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; +import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; +import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; +import { IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; +import { MockFilesystem } from '../testUtils/mockFilesystem.js'; /** * Helper class to assert the properties of a link. @@ -79,10 +79,10 @@ class ExpectedLink { * @param links Links to assert. * @param expectedLinks Expected links to compare against. */ -const assertLinks = ( +function assertLinks( links: readonly IPromptFileReference[], expectedLinks: readonly ExpectedLink[], -) => { +) { for (let i = 0; i < links.length; i++) { try { expectedLinks[i].assertEqual(links[i]); @@ -96,7 +96,7 @@ const assertLinks = ( expectedLinks.length, `Links count must be correct.`, ); -}; +} suite('PromptsService', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -133,8 +133,8 @@ suite('PromptsService', () => { service = disposables.add(instaService.createInstance(PromptsService)); }); - suite('• getParserFor', () => { - test('• provides cached parser instance', async () => { + suite('getParserFor', () => { + test('provides cached parser instance', async () => { // both languages must yield the same result const languageId = (randomBoolean()) ? PROMPT_LANGUAGE_ID @@ -190,7 +190,7 @@ suite('PromptsService', () => { ); // wait for some random amount of time - await waitRandom(5); + await timeout(5); /** * Next, get parser for the same exact model and @@ -223,7 +223,7 @@ suite('PromptsService', () => { )); // wait for some random amount of time - await waitRandom(5); + await timeout(5); const parser2 = service.getSyntaxParserFor(model2); @@ -306,7 +306,7 @@ suite('PromptsService', () => { ); // wait for some random amount of time - await waitRandom(5); + await timeout(5); /** * Dispose the first parser, perform basic validations, and confirm @@ -378,7 +378,7 @@ suite('PromptsService', () => { ); // wait for some random amount of time - await waitRandom(5); + await timeout(5); /** * This time dispose model of the second parser instead of @@ -459,7 +459,7 @@ suite('PromptsService', () => { ); }); - test('• auto-updated on model changes', async () => { + test('auto-updated on model changes', async () => { const langId = 'bazLang'; const model = disposables.add(createTextModel( @@ -527,7 +527,7 @@ suite('PromptsService', () => { ); }); - test('• throws if a disposed model provided', async function () { + test('throws if a disposed model provided', async function () { const model = disposables.add(createTextModel( 'test1\ntest2\n\ntest3\t\n', 'barLang', @@ -544,8 +544,8 @@ suite('PromptsService', () => { }); }); - suite('• getAllMetadata', () => { - test('• explicit', async function () { + suite('getAllMetadata', () => { + test('explicit', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; @@ -702,12 +702,12 @@ suite('PromptsService', () => { }); }); - suite('• findInstructionFilesFor', () => { + suite('findInstructionFilesFor', () => { teardown(() => { sinon.restore(); }); - test('• finds correct instruction files', async () => { + test('finds correct instruction files', async () => { const rootFolderName = 'finds-instruction-files'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); @@ -874,7 +874,7 @@ suite('PromptsService', () => { ]); assert.deepStrictEqual( - instructions.map(pick('path')), + instructions.map(i => i.path), [ // local instructions URI.joinPath(rootFolderUri, '.github/prompts/file1.instructions.md').path, @@ -886,7 +886,7 @@ suite('PromptsService', () => { ); }); - test('• does not have duplicates', async () => { + test('does not have duplicates', async () => { const rootFolderName = 'finds-instruction-files-without-duplicates'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); @@ -1055,7 +1055,7 @@ suite('PromptsService', () => { ]); assert.deepStrictEqual( - instructions.map(pick('path')), + instructions.map(i => i.path), [ // local instructions URI.joinPath(rootFolderUri, '.github/prompts/file1.instructions.md').path, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.test.ts index 05f1b5741f3..097af623225 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.test.ts @@ -46,11 +46,11 @@ interface IExpectedFolder extends IExpectedFilesystemNode { /** * Validates that file at {@link filePath} has expected attributes. */ -const validateFile = async ( +async function validateFile( filePath: string, expectedFile: IExpectedFile, fileService: IFileService, -) => { +) { let readFile: IFileStat | undefined; try { readFile = await fileService.resolve(URI.file(filePath)); @@ -100,16 +100,16 @@ const validateFile = async ( expectedFile.contents, `File '${expectedFile.resource.fsPath}' must have correct contents.`, ); -}; +} /** * Validates that folder at {@link folderPath} has expected attributes. */ -const validateFolder = async ( +async function validateFolder( folderPath: string, expectedFolder: IExpectedFolder, fileService: IFileService, -) => { +): Promise { let readFolder: IFileStat | undefined; try { readFolder = await fileService.resolve(URI.file(folderPath)); @@ -177,7 +177,7 @@ const validateFolder = async ( fileService, ); } -}; +} suite('MockFilesystem', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -195,7 +195,7 @@ suite('MockFilesystem', () => { instantiationService.stub(IFileService, fileService); }); - test('• mocks file structure', async () => { + test('mocks file structure', async () => { const mockFilesystem = instantiationService.createInstance(MockFilesystem, [ { name: '/root/folder', diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts index 09d00b4c1dc..dc0a44da5c7 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts @@ -6,7 +6,7 @@ import { URI } from '../../../../../../../base/common/uri.js'; import { assert } from '../../../../../../../base/common/assert.js'; import { VSBuffer } from '../../../../../../../base/common/buffer.js'; -import { wait } from '../../../../../../../base/test/common/testUtils.js'; +import { timeout } from '../../../../../../../base/common/async.js'; import { IFileService } from '../../../../../../../platform/files/common/files.js'; /** @@ -58,7 +58,7 @@ export class MockFilesystem { // wait for the filesystem event to settle before proceeding // this is temporary workaround and should be fixed once we // improve behavior of the `settled()` / `allSettled()` methods - await wait(25); + await timeout(25); return result; } diff --git a/src/vs/platform/prompts/test/common/utils/mock.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/mock.test.ts similarity index 88% rename from src/vs/platform/prompts/test/common/utils/mock.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/mock.test.ts index 0539b73bde4..460e96e892e 100644 --- a/src/vs/platform/prompts/test/common/utils/mock.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/mock.test.ts @@ -5,16 +5,16 @@ import assert from 'assert'; import { mockObject, mockService } from './mock.js'; -import { typeCheck } from '../../../../../base/common/types.js'; -import { randomInt } from '../../../../../base/common/numbers.js'; -import { randomBoolean } from '../../../../../base/test/common/testUtils.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { typeCheck } from '../../../../../../../base/common/types.js'; +import { randomInt } from '../../../../../../../base/common/numbers.js'; +import { randomBoolean } from '../../../../../../../base/test/common/testUtils.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; suite('mockService', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('• mockObject', () => { - test('• overrides properties and functions', () => { + suite('mockObject', () => { + test('overrides properties and functions', () => { interface ITestObject { foo: string; bar: string; @@ -67,7 +67,7 @@ suite('mockService', () => { }); }); - test('• immutability of the overrides object', () => { + test('immutability of the overrides object', () => { interface ITestObject { foo: string; bar: string; @@ -101,8 +101,8 @@ suite('mockService', () => { }); }); - suite('• mockService', () => { - test('• overrides properties and functions', () => { + suite('mockService', () => { + test('overrides properties and functions', () => { interface ITestService { readonly _serviceBrand: undefined; prop1: string; @@ -156,7 +156,7 @@ suite('mockService', () => { }); }); - test('• immutability of the overrides object', () => { + test('immutability of the overrides object', () => { interface ITestService { readonly _serviceBrand: undefined; foo: string; diff --git a/src/vs/platform/prompts/test/common/utils/mock.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/mock.ts similarity index 93% rename from src/vs/platform/prompts/test/common/utils/mock.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/mock.ts index 26b870312b2..6e480a1f43c 100644 --- a/src/vs/platform/prompts/test/common/utils/mock.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/mock.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assert } from '../../../../../base/common/assert.js'; -import { isOneOf } from '../../../../../base/common/types.js'; +import { assert } from '../../../../../../../base/common/assert.js'; +import { isOneOf } from '../../../../../../../base/common/types.js'; + + /** * Mocks an `TObject` with the provided `overrides`. diff --git a/src/vs/base/test/common/objectCache.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/objectCache.test.ts similarity index 95% rename from src/vs/base/test/common/objectCache.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/objectCache.test.ts index fb80b91c49e..e8c538f5e08 100644 --- a/src/vs/base/test/common/objectCache.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/objectCache.test.ts @@ -5,10 +5,10 @@ import assert from 'assert'; import { spy } from 'sinon'; -import { ObjectCache } from '../../common/objectCache.js'; -import { wait } from '../../../base/test/common/testUtils.js'; -import { ObservableDisposable } from '../../common/observableDisposable.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js'; +import { ObjectCache } from '../../../../common/promptSyntax/utils/objectCache.js'; +import { timeout } from '../../../../../../../base/common/async.js'; +import { ObservableDisposable } from '../../../../common/promptSyntax/utils/observableDisposable.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; /** * Test object class. @@ -126,7 +126,7 @@ suite('ObjectCache', function () { obj3.dispose(); // the object is removed from the cache asynchronously // so add a small delay to ensure the object is removed - await wait(5); + await timeout(5); const obj5 = cache.get(key1); assert( diff --git a/src/vs/base/test/common/observableDisposable.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/observableDisposable.test.ts similarity index 86% rename from src/vs/base/test/common/observableDisposable.test.ts rename to src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/observableDisposable.test.ts index afc90077fd8..2478b0136d4 100644 --- a/src/vs/base/test/common/observableDisposable.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/observableDisposable.test.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; import { spy } from 'sinon'; -import { wait, waitRandom } from './testUtils.js'; -import { randomInt } from '../../common/numbers.js'; -import { Disposable, IDisposable } from '../../common/lifecycle.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; -import { assertNotDisposed, ObservableDisposable } from '../../common/observableDisposable.js'; +import { timeout } from '../../../../../../../base/common/async.js'; +import { randomInt } from '../../../../../../../base/common/numbers.js'; +import { Disposable, IDisposable } from '../../../../../../../base/common/lifecycle.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { assertNotDisposed, ObservableDisposable } from '../../../../common/promptSyntax/utils/observableDisposable.js'; suite('ObservableDisposable', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); - test('• tracks `disposed` state', () => { + test('tracks `disposed` state', () => { // this is an abstract class, so we have to create // an anonymous class that extends it const object = new class extends ObservableDisposable { }(); @@ -42,8 +42,8 @@ suite('ObservableDisposable', () => { ); }); - suite('• onDispose()', () => { - test('• fires the event on dispose', async () => { + suite('onDispose()', () => { + test('fires the event on dispose', async () => { // this is an abstract class, so we have to create // an anonymous class that extends it const object = new class extends ObservableDisposable { }(); @@ -62,7 +62,7 @@ suite('ObservableDisposable', () => { '`onDispose` callback must not be called yet.', ); - await waitRandom(10); + await timeout(10); assert( onDisposeSpy.notCalled, @@ -71,7 +71,7 @@ suite('ObservableDisposable', () => { // dispose object and wait for the event to be fired/received object.dispose(); - await wait(1); + await timeout(1); /** * Validate that the callback was called. @@ -93,7 +93,7 @@ suite('ObservableDisposable', () => { object.dispose(); object.dispose(); - await waitRandom(10, 5); + await timeout(10); object.dispose(); assert( @@ -107,7 +107,7 @@ suite('ObservableDisposable', () => { ); }); - test('• executes callback immediately if already disposed', async () => { + test('executes callback immediately if already disposed', async () => { // this is an abstract class, so we have to create // an anonymous class that extends it const object = new class extends ObservableDisposable { }(); @@ -115,23 +115,23 @@ suite('ObservableDisposable', () => { // dispose object and wait for the event to be fired/received object.dispose(); - await wait(10); + await timeout(10); const onDisposeSpy = spy(); disposables.add(object.onDispose(onDisposeSpy)); - await wait(10); + await timeout(10); assert( onDisposeSpy.calledOnce, '`onDispose` callback must be called immediately.', ); - await waitRandom(10, 5); + await timeout(10); disposables.add(object.onDispose(onDisposeSpy)); - await wait(10); + await timeout(10); assert( onDisposeSpy.calledTwice, @@ -140,7 +140,7 @@ suite('ObservableDisposable', () => { // dispose object and wait for the event to be fired/received object.dispose(); - await wait(10); + await timeout(10); assert( onDisposeSpy.calledTwice, @@ -149,8 +149,8 @@ suite('ObservableDisposable', () => { }); }); - suite('• addDisposable()', () => { - test('• disposes provided object with itself', async () => { + suite('addDisposable()', () => { + test('disposes provided object with itself', async () => { class TestDisposable implements IDisposable { private _disposed = false; public get disposed() { @@ -208,7 +208,7 @@ suite('ObservableDisposable', () => { ); }); - test('• disposes the entire tree of disposables', async () => { + test('disposes the entire tree of disposables', async () => { class TestDisposable extends ObservableDisposable { } /** @@ -290,8 +290,8 @@ suite('ObservableDisposable', () => { }); }); - suite('• asserts', () => { - test('• not disposed (method)', async () => { + suite('asserts', () => { + test('not disposed (method)', async () => { // this is an abstract class, so we have to create // an anonymous class that extends it const object: ObservableDisposable = new class extends ObservableDisposable { }(); @@ -301,7 +301,7 @@ suite('ObservableDisposable', () => { object.assertNotDisposed('Object must not be disposed.'); }); - await waitRandom(10); + await timeout(10); assert.doesNotThrow(() => { object.assertNotDisposed('Object must not be disposed.'); @@ -309,20 +309,20 @@ suite('ObservableDisposable', () => { // dispose object and wait for the event to be fired/received object.dispose(); - await wait(1); + await timeout(1); assert.throws(() => { object.assertNotDisposed('Object must not be disposed.'); }); - await waitRandom(10); + await timeout(10); assert.throws(() => { object.assertNotDisposed('Object must not be disposed.'); }); }); - test('• not disposed (function)', async () => { + test('not disposed (function)', async () => { // this is an abstract class, so we have to create // an anonymous class that extends it const object: ObservableDisposable = new class extends ObservableDisposable { }(); @@ -335,7 +335,7 @@ suite('ObservableDisposable', () => { ); }); - await waitRandom(10); + await timeout(10); assert.doesNotThrow(() => { assertNotDisposed( @@ -346,7 +346,7 @@ suite('ObservableDisposable', () => { // dispose object and wait for the event to be fired/received object.dispose(); - await wait(1); + await timeout(1); assert.throws(() => { assertNotDisposed( @@ -355,7 +355,7 @@ suite('ObservableDisposable', () => { ); }); - await waitRandom(10); + await timeout(10); assert.throws(() => { assertNotDisposed( diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts index 86b9a78c99f..61643956c62 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts @@ -4,33 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; +import { CancellationToken } from '../../../../../../../base/common/cancellation.js'; +import { match } from '../../../../../../../base/common/glob.js'; import { Schemas } from '../../../../../../../base/common/network.js'; import { basename, relativePath } from '../../../../../../../base/common/resources.js'; -import { IMockFolder, MockFilesystem } from '../testUtils/mockFilesystem.js'; -import { mockObject } from '../../../../../../../base/test/common/testUtils.js'; -import { IFileService } from '../../../../../../../platform/files/common/files.js'; -import { PromptsConfig } from '../../../../../../../platform/prompts/common/config.js'; -import { FileService } from '../../../../../../../platform/files/common/fileService.js'; -import { mockService } from '../../../../../../../platform/prompts/test/common/utils/mock.js'; -import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { mock } from '../../../../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; -import { isValidGlob, PromptFilesLocator } from '../../../../common/promptSyntax/utils/promptFilesLocator.js'; +import { IConfigurationOverrides, IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { IFileService } from '../../../../../../../platform/files/common/files.js'; +import { FileService } from '../../../../../../../platform/files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; -import { IConfigurationOverrides, IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from '../../../../../../../platform/workspace/common/workspace.js'; -import { PromptsType } from '../../../../../../../platform/prompts/common/prompts.js'; -import { CancellationToken } from '../../../../../../../base/common/cancellation.js'; import { IWorkbenchEnvironmentService } from '../../../../../../services/environment/common/environmentService.js'; +import { IFileMatch, IFileQuery, ISearchService } from '../../../../../../services/search/common/search.js'; import { IUserDataProfileService } from '../../../../../../services/userDataProfile/common/userDataProfile.js'; -import { ISearchService, IFileQuery, IFileMatch } from '../../../../../../services/search/common/search.js'; -import { URI } from '../../../../../../../base/common/uri.js'; -import { match } from '../../../../../../../base/common/glob.js'; +import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; +import { PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; +import { isValidGlob, PromptFilesLocator } from '../../../../common/promptSyntax/utils/promptFilesLocator.js'; +import { IMockFolder, MockFilesystem } from '../testUtils/mockFilesystem.js'; +import { mockService } from './mock.js'; /** * Mocked instance of {@link IConfigurationService}. */ -const mockConfigService = (value: T): IConfigurationService => { +function mockConfigService(value: T): IConfigurationService { return mockService({ getValue(key?: string | IConfigurationOverrides) { assert( @@ -49,24 +49,24 @@ const mockConfigService = (value: T): IConfigurationService => { return value; }, }); -}; +} /** * Mocked instance of {@link IWorkspaceContextService}. */ -const mockWorkspaceService = (folders: IWorkspaceFolder[]): IWorkspaceContextService => { +function mockWorkspaceService(folders: IWorkspaceFolder[]): IWorkspaceContextService { return mockService({ getWorkspace(): IWorkspace { - return mockObject({ - folders, - }); + return new class extends mock() { + override folders = folders; + }; }, getWorkspaceFolder(): IWorkspaceFolder | null { return null; } }); -}; +} suite('PromptFilesLocator', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -104,11 +104,11 @@ suite('PromptFilesLocator', () => { const workspaceFolders = workspaceFolderPaths.map((path, index) => { const uri = URI.file(path); - return mockObject({ - uri, - name: basename(uri), - index, - }); + return new class extends mock() { + override uri = uri; + override name = basename(uri); + override index = index; + }; }); instantiationService.stub(IWorkspaceContextService, mockWorkspaceService(workspaceFolders)); instantiationService.stub(IWorkbenchEnvironmentService, {} as IWorkbenchEnvironmentService); @@ -148,11 +148,11 @@ suite('PromptFilesLocator', () => { return instantiationService.createInstance(PromptFilesLocator); }; - suite('• empty workspace', () => { + suite('empty workspace', () => { const EMPTY_WORKSPACE: string[] = []; - suite('• empty filesystem', () => { - test('• no config value', async () => { + suite('empty filesystem', () => { + test('no config value', async () => { const locator = await createPromptsLocator(undefined, EMPTY_WORKSPACE, []); assertOutcome( @@ -163,7 +163,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• object config value', async () => { + test('object config value', async () => { const locator = await createPromptsLocator({ '/Users/legomushroom/repos/prompts/': true, '/tmp/prompts/': false, @@ -177,7 +177,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• array config value', async () => { + test('array config value', async () => { const locator = await createPromptsLocator([ 'relative/path/to/prompts/', '/abs/path', @@ -191,7 +191,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• null config value', async () => { + test('null config value', async () => { const locator = await createPromptsLocator(null, EMPTY_WORKSPACE, []); assertOutcome( @@ -202,7 +202,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• string config value', async () => { + test('string config value', async () => { const locator = await createPromptsLocator('/etc/hosts/prompts', EMPTY_WORKSPACE, []); assertOutcome( @@ -214,8 +214,8 @@ suite('PromptFilesLocator', () => { }); }); - suite('• non-empty filesystem', () => { - test('• core logic', async () => { + suite('non-empty filesystem', () => { + test('core logic', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/prompts': true, @@ -270,8 +270,8 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - suite('• absolute', () => { - suite('• wild card', () => { + suite('absolute', () => { + suite('wild card', () => { const settings = [ '/Users/legomushroom/repos/vscode/**', '/Users/legomushroom/repos/vscode/**/*.prompt.md', @@ -512,10 +512,10 @@ suite('PromptFilesLocator', () => { }); }); - suite('• single-root workspace', () => { - suite('• glob pattern', () => { - suite('• relative', () => { - suite('• wild card', () => { + suite('single-root workspace', () => { + suite('glob pattern', () => { + suite('relative', () => { + suite('wild card', () => { const testSettings = [ '**', '**/*.prompt.md', @@ -754,8 +754,8 @@ suite('PromptFilesLocator', () => { }); }); - suite('• absolute', () => { - suite('• wild card', () => { + suite('absolute', () => { + suite('wild card', () => { const settings = [ '/Users/legomushroom/repos/vscode/**', '/Users/legomushroom/repos/vscode/**/*.prompt.md', @@ -996,7 +996,7 @@ suite('PromptFilesLocator', () => { }); }); - test('• core logic', async () => { + test('core logic', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/prompts': true, @@ -1078,7 +1078,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• with disabled `.github/prompts` location', async () => { + test('with disabled `.github/prompts` location', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/prompts': true, @@ -1164,9 +1164,9 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - suite('• multi-root workspace', () => { - suite('• core logic', () => { - test('• without top-level `.github` folder', async () => { + suite('multi-root workspace', () => { + suite('core logic', () => { + test('without top-level `.github` folder', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/prompts': true, @@ -1286,7 +1286,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• with top-level `.github` folder', async () => { + test('with top-level `.github` folder', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/prompts': true, @@ -1409,7 +1409,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• with disabled `.github/prompts` location', async () => { + test('with disabled `.github/prompts` location', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/prompts': true, @@ -1529,7 +1529,7 @@ suite('PromptFilesLocator', () => { locator.dispose(); }); - test('• mixed', async () => { + test('mixed', async () => { const locator = await createPromptsLocator( { '/Users/legomushroom/repos/**/*test*': true, @@ -1660,9 +1660,9 @@ suite('PromptFilesLocator', () => { }); }); - suite('• glob pattern', () => { - suite('• relative', () => { - suite('• wild card', () => { + suite('glob pattern', () => { + suite('relative', () => { + suite('wild card', () => { const testSettings = [ '**', '**/*.prompt.md', @@ -1980,8 +1980,8 @@ suite('PromptFilesLocator', () => { }); }); - suite('• absolute', () => { - suite('• wild card', () => { + suite('absolute', () => { + suite('wild card', () => { const testSettings = [ '/Users/legomushroom/repos/**', '/Users/legomushroom/repos/**/*.prompt.md', @@ -2343,8 +2343,8 @@ suite('PromptFilesLocator', () => { }); }); - suite('• isValidGlob', () => { - test('• valid patterns', () => { + suite('isValidGlob', () => { + test('valid patterns', () => { const globs = [ '**', '\*', @@ -2380,7 +2380,7 @@ suite('PromptFilesLocator', () => { } }); - test('• invalid patterns', () => { + test('invalid patterns', () => { const globs = [ '.', '\\*', @@ -2418,8 +2418,8 @@ suite('PromptFilesLocator', () => { }); }); - suite('• getConfigBasedSourceFolders', () => { - test('• gets unambiguous list of folders', async () => { + suite('getConfigBasedSourceFolders', () => { + test('gets unambiguous list of folders', async () => { const locator = await createPromptsLocator( { '.github/prompts': true, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/treeUtils.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/treeUtils.test.ts index f45cae7dd6b..38d070a4d91 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/treeUtils.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/treeUtils.test.ts @@ -6,16 +6,16 @@ import assert from 'assert'; import { randomInt } from '../../../../../../../base/common/numbers.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; -import { CompositeToken } from '../../../../../../../editor/common/codecs/compositeToken.js'; +import { BaseToken } from '../../../../common/promptSyntax/codecs/base/baseToken.js'; +import { CompositeToken } from '../../../../common/promptSyntax/codecs/base/compositeToken.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { curry, difference, flatten, forEach, map, TTree } from '../../../../common/promptSyntax/utils/treeUtils.js'; -import { ExclamationMark, Space, Tab, VerticalTab, Word } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/index.js'; +import { ExclamationMark, Space, Tab, VerticalTab, Word } from '../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; suite('tree utilities', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('• flatten', () => { + test('flatten', () => { const tree = { id: '1', children: [ @@ -61,8 +61,8 @@ suite('tree utilities', () => { assert.deepStrictEqual(flatten({}), [{}]); }); - suite('• forEach', () => { - test('• iterates though all nodes', () => { + suite('forEach', () => { + test('iterates though all nodes', () => { const tree = { id: '1', children: [ @@ -120,7 +120,7 @@ suite('tree utilities', () => { ); }); - test('• can be stopped prematurely', () => { + test('can be stopped prematurely', () => { const tree = { id: '1', children: [ @@ -185,8 +185,8 @@ suite('tree utilities', () => { }); }); - suite('• map', () => { - test('• maps a tree', () => { + suite('map', () => { + test('maps a tree', () => { interface ITree { id: string; children?: ITree[]; @@ -295,7 +295,7 @@ suite('tree utilities', () => { ); }); - test('• callback can control resulting children', () => { + test('callback can control resulting children', () => { interface ITree { id: string; children?: ITree[]; @@ -457,7 +457,7 @@ suite('tree utilities', () => { }); }); - test('• curry', () => { + test('curry', () => { const originalFunction = (a: number, b: number, c: number) => { return a + b + c; }; @@ -484,7 +484,7 @@ suite('tree utilities', () => { } }); - suite('• difference', () => { + suite('difference', () => { class TestCompositeToken extends CompositeToken> { public override toString(): string { return `CompositeToken:\n${BaseToken.render(this.children, '\n')})`; @@ -492,7 +492,7 @@ suite('tree utilities', () => { } - test('• tree roots differ (no children)', () => { + test('tree roots differ (no children)', () => { const tree1 = new Word(new Range(1, 1, 1, 1 + 5), 'hello'); const tree2 = new Word(new Range(1, 1, 1, 1 + 5), 'halou'); @@ -507,7 +507,7 @@ suite('tree utilities', () => { ); }); - test('• returns tree difference (single children level)', () => { + test('returns tree difference (single children level)', () => { const tree1 = asTreeNode>( new Word(new Range(1, 1, 1, 1 + 5), 'hello'), [ @@ -548,7 +548,7 @@ suite('tree utilities', () => { ); }); - test('• returns tree difference (multiple children levels)', () => { + test('returns tree difference (multiple children levels)', () => { const compositeToken1 = new TestCompositeToken([ new VerticalTab(new Range(1, 13, 1, 14)), new Space(new Range(1, 14, 1, 15)), @@ -610,7 +610,7 @@ suite('tree utilities', () => { ); }); - test('• returns null for equal trees', () => { + test('returns null for equal trees', () => { const tree1 = new TestCompositeToken([ asTreeNode(new Word( new Range(1, 1, 1, 1 + 5), diff --git a/src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts b/src/vs/workbench/contrib/chat/test/electron-browser/voiceChatActions.test.ts similarity index 95% rename from src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts rename to src/vs/workbench/contrib/chat/test/electron-browser/voiceChatActions.test.ts index 478a0c14214..bcf00d4b79f 100644 --- a/src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts +++ b/src/vs/workbench/contrib/chat/test/electron-browser/voiceChatActions.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { parseNextChatResponseChunk } from '../../electron-sandbox/actions/voiceChatActions.js'; +import { parseNextChatResponseChunk } from '../../electron-browser/actions/voiceChatActions.js'; suite('VoiceChatActions', function () { diff --git a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index a7a7ce4c176..9d2c63f2e51 100644 --- a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -132,10 +132,10 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon this._onDidChangeSchemaContributions.fire(); })); - keybindingService.registerSchemaContribution({ + this._register(keybindingService.registerSchemaContribution({ getSchemaAdditions: () => this.getKeybindingSchemaAdditions(), onDidChange: this._onDidChangeSchemaContributions.event, - }); + })); } private getAllProvidedCodeActionKinds(): Array { diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index d5e53fcba54..12178f16a0f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -25,7 +25,7 @@ import { Selection } from '../../../../../editor/common/core/selection.js'; import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { registerAction2 } from '../../../../../platform/actions/common/actions.js'; -import { assertIsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { ActionBar } from '../../../../../base/browser/ui/actionbar/actionbar.js'; import { toAction } from '../../../../../base/common/actions.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; @@ -225,7 +225,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { let lastReplaceTextLength = 0; const replaceText = (text: string, isPreview: boolean) => { if (!previewStart) { - previewStart = assertIsDefined(this.editor.getPosition()); + previewStart = assertReturnsDefined(this.editor.getPosition()); } const endPosition = new Position(previewStart.lineNumber, previewStart.column + text.length); diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index 20d03403370..d366a6c17b2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -47,7 +47,7 @@ export function getSimpleEditorOptions(configurationService: IConfigurationServi }, accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), cursorBlinking: configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking'), - experimentalEditContextEnabled: configurationService.getValue('editor.experimentalEditContextEnabled'), + editContext: configurationService.getValue('editor.editContext'), defaultColorDecorators: 'never', }; } diff --git a/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts index 80aa77d2019..739ae4a7706 100644 --- a/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts @@ -161,11 +161,22 @@ export class LanguageConfigurationFileHandler extends Disposable { let result: CommentRule | undefined = undefined; if (typeof source.lineComment !== 'undefined') { - if (typeof source.lineComment !== 'string') { - console.warn(`[${languageId}]: language configuration: expected \`comments.lineComment\` to be a string.`); - } else { + if (typeof source.lineComment === 'string') { result = result || {}; result.lineComment = source.lineComment; + } else if (types.isObject(source.lineComment)) { + const lineCommentObj = source.lineComment as any; + if (typeof lineCommentObj.comment === 'string') { + result = result || {}; + result.lineComment = { + comment: lineCommentObj.comment, + noIndent: lineCommentObj.noIndent + }; + } else { + console.warn(`[${languageId}]: language configuration: expected \`comments.lineComment.comment\` to be a string.`); + } + } else { + console.warn(`[${languageId}]: language configuration: expected \`comments.lineComment\` to be a string or an object with comment property.`); } } if (typeof source.blockComment !== 'undefined') { @@ -519,7 +530,7 @@ const schema: IJSONSchema = { comments: { default: { blockComment: ['/*', '*/'], - lineComment: '//' + lineComment: { comment: '//', noIndent: false } }, description: nls.localize('schema.comments', 'Defines the comment symbols'), type: 'object', @@ -536,8 +547,21 @@ const schema: IJSONSchema = { }] }, lineComment: { - type: 'string', - description: nls.localize('schema.lineComment', 'The character sequence that starts a line comment.') + type: 'object', + description: nls.localize('schema.lineComment.object', 'Configuration for line comments.'), + properties: { + comment: { + type: 'string', + description: nls.localize('schema.lineComment.comment', 'The character sequence that starts a line comment.') + }, + noIndent: { + type: 'boolean', + description: nls.localize('schema.lineComment.noIndent', 'Whether the comment token should not be indented and placed at the first column. Defaults to false.'), + default: false + } + }, + required: ['comment'], + additionalProperties: false } } }, diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/displayChangeRemeasureFonts.ts similarity index 100% rename from src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/displayChangeRemeasureFonts.ts diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts similarity index 100% rename from src/vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts similarity index 100% rename from src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/sleepResumeRepaintMinimap.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts similarity index 100% rename from src/vs/workbench/contrib/codeEditor/electron-sandbox/sleepResumeRepaintMinimap.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts similarity index 98% rename from src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts index 645eabd6384..5a746dc17fc 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts @@ -16,7 +16,7 @@ import { ICodeEditorService } from '../../../../editor/browser/services/codeEdit import { ITextModel } from '../../../../editor/common/model.js'; import { Constants } from '../../../../base/common/uint.js'; import { IHostService } from '../../../services/host/browser/host.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { ILoggerService } from '../../../../platform/log/common/log.js'; import { joinPath } from '../../../../base/common/resources.js'; import { IFileService } from '../../../../platform/files/common/files.js'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index d246fb9fdda..ee708d0ddda 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -12,7 +12,7 @@ import { IReference } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { basename } from '../../../../base/common/path.js'; import { dirname, isEqual } from '../../../../base/common/resources.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -338,7 +338,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { if (!this._modelRef) { const oldCapabilities = this.capabilities; - this._modelRef = this._register(assertIsDefined(await this.customEditorService.models.tryRetain(this.resource, this.viewType))); + this._modelRef = this._register(assertReturnsDefined(await this.customEditorService.models.tryRetain(this.resource, this.viewType))); this._register(this._modelRef.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._register(this._modelRef.object.onDidChangeReadonly(() => this._onDidChangeCapabilities.fire())); // If we're loading untitled file data we should ensure it's dirty @@ -362,12 +362,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public undo(): void | Promise { - assertIsDefined(this._modelRef); + assertReturnsDefined(this._modelRef); return this.undoRedoService.undo(this.resource); } public redo(): void | Promise { - assertIsDefined(this._modelRef); + assertReturnsDefined(this._modelRef); return this.undoRedoService.redo(this.resource); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index d9bd443af2c..f473e0a6b74 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -9,7 +9,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { extname, isEqual } from '../../../../base/common/resources.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { RedoCommand, UndoCommand } from '../../../../editor/browser/editorExtensions.js'; import { IResourceEditorInput } from '../../../../platform/editor/common/editor.js'; @@ -157,8 +157,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ editorID: string, group: IEditorGroup ): DiffEditorInput { - const modifiedOverride = CustomEditorInput.create(this.instantiationService, assertIsDefined(editor.modified.resource), editorID, group.id, { customClasses: 'modified' }); - const originalOverride = CustomEditorInput.create(this.instantiationService, assertIsDefined(editor.original.resource), editorID, group.id, { customClasses: 'original' }); + const modifiedOverride = CustomEditorInput.create(this.instantiationService, assertReturnsDefined(editor.modified.resource), editorID, group.id, { customClasses: 'modified' }); + const originalOverride = CustomEditorInput.create(this.instantiationService, assertReturnsDefined(editor.original.resource), editorID, group.id, { customClasses: 'original' }); return this.instantiationService.createInstance(DiffEditorInput, editor.label, editor.description, originalOverride, modifiedOverride, true); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 929118f46c0..7a2073c81d4 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -730,6 +730,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { if (this.range && !this.range.equalsRange(range)) { this.range = range; this.editor.layoutContentWidget(this); + this.updateSize(); } })); this.create(cssClass); @@ -767,21 +768,22 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { }); })); - const updateSize = () => { - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - this.domNode.style.height = `${lineHeight}px`; - this.domNode.style.width = `${Math.ceil(0.8 * lineHeight)}px`; - this.domNode.style.marginLeft = `4px`; - }; - updateSize(); + this.updateSize(); this.toDispose.push(this.editor.onDidChangeConfiguration(c => { if (c.hasChanged(EditorOption.fontSize) || c.hasChanged(EditorOption.lineHeight)) { - updateSize(); + this.updateSize(); } })); } + private updateSize() { + const lineHeight = this.range ? this.editor.getLineHeightForPosition(this.range.getStartPosition()) : this.editor.getOption(EditorOption.lineHeight); + this.domNode.style.height = `${lineHeight}px`; + this.domNode.style.width = `${Math.ceil(0.8 * lineHeight)}px`; + this.domNode.style.marginLeft = `4px`; + } + @memoize getId(): string { return generateUuid(); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 07b92710f72..84524c76602 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -421,6 +421,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi options.lineHeight = editorConfig.lineHeight; options.fontLigatures = editorConfig.fontLigatures; options.ariaLabel = this.placeholder; + options.allowVariableLineHeights = false; return options; } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index a3083b15868..e853bcdeed5 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -74,7 +74,7 @@ function assignStackFrameContext(element: StackFrame, context: any) { return context; } -export function getContext(element: CallStackItem | null): any { +export function getContext(element: CallStackItem | null) { if (element instanceof StackFrame) { return assignStackFrameContext(element, {}); } else if (element instanceof Thread) { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 255c41c225d..4a98431a021 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -28,7 +28,7 @@ import { IViewContainersRegistry, IViewsRegistry, ViewContainer, ViewContainerLo import { launchSchemaId } from '../../../services/configuration/common/configuration.js'; import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL } from '../../notebook/browser/contrib/notebookVariables/notebookVariableCommands.js'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_VARIABLE_VALUE, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from '../common/debug.js'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_THREADS_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_VARIABLE_VALUE, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from '../common/debug.js'; import { DebugWatchAccessibilityAnnouncer } from '../common/debugAccessibilityAnnouncer.js'; import { DebugContentProvider } from '../common/debugContentProvider.js'; import { DebugLifecycle } from '../common/debugLifecycle.js'; @@ -123,7 +123,7 @@ const registerDebugCommandPaletteItem = (id: string, title: ICommandActionTitle, }; registerDebugCommandPaletteItem(RESTART_SESSION_ID, RESTART_LABEL); -registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize2('terminateThread', "Terminate Thread"), CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize2('terminateThread', "Terminate Thread"), CONTEXT_IN_DEBUG_MODE, CONTEXT_TERMINATE_THREADS_SUPPORTED); registerDebugCommandPaletteItem(STEP_OVER_ID, STEP_OVER_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.and(CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); @@ -144,7 +144,7 @@ registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKe registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL); registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL); -registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_LOADED_SCRIPTS_SUPPORTED); registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL); registerDebugCommandPaletteItem(SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL); registerDebugCommandPaletteItem(CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); @@ -176,7 +176,7 @@ registerDebugViewMenuItem(MenuId.DebugCallStackContext, CONTINUE_ID, CONTINUE_LA registerDebugViewMenuItem(MenuId.DebugCallStackContext, STEP_OVER_ID, STEP_OVER_LABEL, 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugViewMenuItem(MenuId.DebugCallStackContext, STEP_INTO_ID, STEP_INTO_LABEL, 30, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugViewMenuItem(MenuId.DebugCallStackContext, STEP_OUT_ID, STEP_OUT_LABEL, 40, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugViewMenuItem(MenuId.DebugCallStackContext, TERMINATE_THREAD_ID, nls.localize('terminateThread', "Terminate Thread"), 10, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), undefined, 'termination'); +registerDebugViewMenuItem(MenuId.DebugCallStackContext, TERMINATE_THREAD_ID, nls.localize('terminateThread', "Terminate Thread"), 10, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_TERMINATE_THREADS_SUPPORTED, 'termination'); registerDebugViewMenuItem(MenuId.DebugCallStackContext, RESTART_FRAME_ID, nls.localize('restartFrame', "Restart Frame"), 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), CONTEXT_RESTART_FRAME_SUPPORTED), CONTEXT_STACK_FRAME_SUPPORTS_RESTART); registerDebugViewMenuItem(MenuId.DebugCallStackContext, COPY_STACK_TRACE_ID, nls.localize('copyStackTrace', "Copy Call Stack"), 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), undefined, '3_modification'); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 930812de0f9..ce162293526 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -737,6 +737,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { const config = this.configurationService.getValue('debug'); options.acceptSuggestionOnEnter = config.console.acceptSuggestionOnEnter === 'on' ? 'on' : 'off'; options.ariaLabel = this.getAriaLabel(); + options.allowVariableLineHeights = false; this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index a2b1a8b2c6b..4487bd487aa 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -89,6 +89,7 @@ export const CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED = new RawContextKey< export const CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED = new RawContextKey('breakWhenValueIsReadSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsReadSupported', "True when the focused breakpoint supports to break when value is read.") }); export const CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED = new RawContextKey('terminateDebuggeeSupported', false, { type: 'boolean', description: nls.localize('terminateDebuggeeSupported', "True when the focused session supports the terminate debuggee capability.") }); export const CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED = new RawContextKey('suspendDebuggeeSupported', false, { type: 'boolean', description: nls.localize('suspendDebuggeeSupported', "True when the focused session supports the suspend debuggee capability.") }); +export const CONTEXT_TERMINATE_THREADS_SUPPORTED = new RawContextKey('terminateThreadsSupported', false, { type: 'boolean', description: nls.localize('terminateThreadsSupported', "True when the focused session supports the terminate threads capability.") }); export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey('variableEvaluateNamePresent', false, { type: 'boolean', description: nls.localize('variableEvaluateNamePresent', "True when the focused variable has an 'evalauteName' field set.") }); export const CONTEXT_VARIABLE_IS_READONLY = new RawContextKey('variableIsReadonly', false, { type: 'boolean', description: nls.localize('variableIsReadonly', "True when the focused variable is read-only.") }); export const CONTEXT_VARIABLE_VALUE = new RawContextKey('variableValue', false, { type: 'string', description: nls.localize('variableValue', "Value of the variable, present for debug visualization clauses.") }); diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 9336c5448d1..20c2092da07 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -5,7 +5,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from './debug.js'; +import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_THREADS_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from './debug.js'; import { isSessionAttach } from './debugUtils.js'; export class ViewModel implements IViewModel { @@ -39,6 +39,7 @@ export class ViewModel implements IViewModel { private multiSessionDebug!: IContextKey; private terminateDebuggeeSupported!: IContextKey; private suspendDebuggeeSupported!: IContextKey; + private terminateThreadsSupported!: IContextKey; private disassembleRequestSupported!: IContextKey; private focusedStackFrameHasInstructionPointerReference!: IContextKey; @@ -58,6 +59,7 @@ export class ViewModel implements IViewModel { this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService); this.terminateDebuggeeSupported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService); this.suspendDebuggeeSupported = CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED.bindTo(contextKeyService); + this.terminateThreadsSupported = CONTEXT_TERMINATE_THREADS_SUPPORTED.bindTo(contextKeyService); this.disassembleRequestSupported = CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED.bindTo(contextKeyService); this.focusedStackFrameHasInstructionPointerReference = CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE.bindTo(contextKeyService); }); @@ -100,6 +102,7 @@ export class ViewModel implements IViewModel { this.setExpressionSupported.set(!!session?.capabilities.supportsSetExpression); this.terminateDebuggeeSupported.set(!!session?.capabilities.supportTerminateDebuggee); this.suspendDebuggeeSupported.set(!!session?.capabilities.supportSuspendDebuggee); + this.terminateThreadsSupported.set(!!session?.capabilities.supportsTerminateThreadsRequest); this.disassembleRequestSupported.set(!!session?.capabilities.supportsDisassembleRequest); this.focusedStackFrameHasInstructionPointerReference.set(!!stackFrame?.instructionPointerReference); const attach = !!session && isSessionAttach(session); diff --git a/src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts similarity index 95% rename from src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts rename to src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index cf036366f08..d229afb5feb 100644 --- a/src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js'; -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from '../../../../platform/debug/common/extensionHostDebugIpc.js'; registerMainProcessRemoteService(IExtensionHostDebugService, ExtensionHostDebugBroadcastChannel.ChannelName, { channelClientCtor: ExtensionHostDebugChannelClient }); diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index 4892338a1c1..1698883b62c 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -15,6 +15,7 @@ import * as nls from '../../../../nls.js'; import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebuggerContribution, IPlatformSpecificAdapterContribution } from '../common/debug.js'; import { AbstractDebugAdapter } from '../common/abstractDebugAdapter.js'; +import { killTree } from '../../../../base/node/processes.js'; /** * An implementation that communicates via two streams with the debug adapter. @@ -288,15 +289,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // processes. Therefore we use TASKKILL.EXE await this.cancelPendingRequests(); if (platform.isWindows) { - return new Promise((c, e) => { - const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess!.pid}`, function (err, stdout, stderr) { - if (err) { - return e(err); - } - }); - killer.on('exit', c); - killer.on('error', e); - }); + return killTree(this.serverProcess!.pid!, true); } else { this.serverProcess.kill('SIGTERM'); return Promise.resolve(undefined); diff --git a/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts b/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts index 648270466ac..29490d17d8b 100644 --- a/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts +++ b/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts @@ -86,10 +86,10 @@ export class DropOrPasteSchemaContribution extends Disposable implements IWorkbe this._onDidChangeSchemaContributions.fire(); })); - keybindingService.registerSchemaContribution({ + this._register(keybindingService.registerSchemaContribution({ getSchemaAdditions: () => this.getKeybindingSchemaAdditions(), onDidChange: this._onDidChangeSchemaContributions.event, - }); + })); } private updateProvidedKinds(): void { diff --git a/src/vs/workbench/contrib/emergencyAlert/electron-sandbox/emergencyAlert.contribution.ts b/src/vs/workbench/contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/emergencyAlert/electron-sandbox/emergencyAlert.contribution.ts rename to src/vs/workbench/contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.ts diff --git a/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts b/src/vs/workbench/contrib/encryption/electron-browser/encryption.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts rename to src/vs/workbench/contrib/encryption/electron-browser/encryption.contribution.ts diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index bcef6a198c0..0b7daf56e12 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { $, append, clearNode } from '../../../../base/browser/dom.js'; +import { $, append, clearNode, addDisposableListener, EventType } from '../../../../base/browser/dom.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { ExtensionIdentifier, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { Orientation, Sizing, SplitView } from '../../../../base/browser/ui/splitview/splitview.js'; @@ -304,15 +304,13 @@ class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeat hoverDisposable.value = undefined; } }; - svg.addEventListener('mousemove', mouseMoveListener); - disposables.add(toDisposable(() => svg.removeEventListener('mousemove', mouseMoveListener))); + disposables.add(addDisposableListener(svg, EventType.MOUSE_MOVE, mouseMoveListener)); const mouseLeaveListener = () => { highlightCircle.style.display = 'none'; hoverDisposable.value = undefined; }; - svg.addEventListener('mouseleave', mouseLeaveListener); - disposables.add(toDisposable(() => svg.removeEventListener('mouseleave', mouseLeaveListener))); + disposables.add(addDisposableListener(svg, EventType.MOUSE_LEAVE, mouseLeaveListener)); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index d05db317edc..b2a54e1f873 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -26,7 +26,6 @@ import { KeyCode } from '../../../../base/common/keyCodes.js'; import { IListStyles } from '../../../../base/browser/ui/list/listWidget.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { IStyleOverride } from '../../../../platform/theme/browser/defaultStyles.js'; -import { getAriaLabelForExtension } from './extensionsViews.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; @@ -37,6 +36,16 @@ import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPan import { DelayedPagedModel, IPagedModel } from '../../../../base/common/paging.js'; import { ExtensionIconWidget } from './extensionsWidgets.js'; +function getAriaLabelForExtension(extension: IExtension | null): string { + if (!extension) { + return ''; + } + const publisher = extension.publisherDomain?.verified ? localize('extension.arialabel.verifiedPublisher', "Verified Publisher {0}", extension.publisherDisplayName) : localize('extension.arialabel.publisher', "Publisher {0}", extension.publisherDisplayName); + const deprecated = extension?.deprecationInfo ? localize('extension.arialabel.deprecated', "Deprecated") : ''; + const rating = extension?.rating ? localize('extension.arialabel.rating', "Rated {0} out of 5 stars by {1} users", extension.rating.toFixed(2), extension.ratingCount) : ''; + return `${extension.displayName}, ${deprecated ? `${deprecated}, ` : ''}${extension.version}, ${publisher}, ${extension.description} ${rating ? `, ${rating}` : ''}`; +} + export class ExtensionsList extends Disposable { readonly list: WorkbenchPagedList; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 8894ec13185..5730221e7fa 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -1549,16 +1549,6 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView imple } -export function getAriaLabelForExtension(extension: IExtension | null): string { - if (!extension) { - return ''; - } - const publisher = extension.publisherDomain?.verified ? localize('extension.arialabel.verifiedPublisher', "Verified Publisher {0}", extension.publisherDisplayName) : localize('extension.arialabel.publisher', "Publisher {0}", extension.publisherDisplayName); - const deprecated = extension?.deprecationInfo ? localize('extension.arialabel.deprecated', "Deprecated") : ''; - const rating = extension?.rating ? localize('extension.arialabel.rating', "Rated {0} out of 5 stars by {1} users", extension.rating.toFixed(2), extension.ratingCount) : ''; - return `${extension.displayName}, ${deprecated ? `${deprecated}, ` : ''}${extension.version}, ${publisher}, ${extension.description} ${rating ? `, ${rating}` : ''}`; -} - export class PreferredExtensionsPagedModel implements IPagedModel { private readonly resolved = new Map(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 830d9fe5280..752dcc3196f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -680,25 +680,56 @@ class Extensions extends Disposable { } } // Detect extensions that do not have a corresponding gallery entry. - // This indicates that it was likely removed from the gallery if (flagExtensionsMissingFromGallery) { + const extensionsToQuery = []; for (const extension of this.local) { + // Extension is already paired with a gallery object + if (extension.gallery) { + continue; + } + // Already flagged as missing from gallery + if (extension.missingFromGallery) { + continue; + } + // A UUID indicates extension originated from gallery if (!extension.identifier.uuid) { continue; } + // Extension is not present in the set we are concerned about if (!flagExtensionsMissingFromGallery.some(f => areSameExtensions(f, extension.identifier))) { continue; } - if (galleryExtensions.find(g => areSameExtensions(g.identifier, extension.identifier))) { - continue; + extensionsToQuery.push(extension); + } + if (extensionsToQuery.length) { + const queryResult = await this.galleryService.getExtensions(extensionsToQuery.map(e => ({ ...e.identifier, version: e.version })), CancellationToken.None); + const queriedIds: string[] = []; + const missingIds: string[] = []; + for (const extension of extensionsToQuery) { + queriedIds.push(extension.identifier.id); + const gallery = queryResult.find(g => areSameExtensions(g.identifier, extension.identifier)); + if (gallery) { + extension.gallery = gallery; + } else { + extension.missingFromGallery = true; + missingIds.push(extension.identifier.id); + } + this._onChange.fire({ extension }); } - const [gallery] = await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], CancellationToken.None); - if (gallery) { - extension.gallery = gallery; - } else { - extension.missingFromGallery = true; - } - this._onChange.fire({ extension }); + type MissingFromGalleryClassification = { + owner: 'joshspicer'; + comment: 'Report when installed extensions are no longer available in the gallery'; + queriedIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions queried as potentially missing from gallery' }; + missingIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions determined missing from gallery' }; + }; + type MissingFromGalleryEvent = { + readonly queriedIds: TelemetryTrustedValue; + readonly missingIds: TelemetryTrustedValue; + }; + this.telemetryService.publicLog2('extensions:missingFromGallery', { + queriedIds: new TelemetryTrustedValue(queriedIds.join(';')), + missingIds: new TelemetryTrustedValue(missingIds.join(';')) + }); } } } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts b/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts similarity index 100% rename from src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts rename to src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 99057a3464c..353643784cc 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -21,7 +21,7 @@ import { IExtensionHostProfileService, ProfileSessionState } from './runtimeExte import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js'; import { IExtensionHostProfile, IExtensionService, ProfileSession } from '../../../services/extensions/common/extensions.js'; -import { ExtensionHostProfiler } from '../../../services/extensions/electron-sandbox/extensionHostProfiler.js'; +import { ExtensionHostProfiler } from '../../../services/extensions/electron-browser/extensionHostProfiler.js'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from '../../../services/statusbar/browser/statusbar.js'; import { URI } from '../../../../base/common/uri.js'; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index e78c2df7839..0ba0fad1d18 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -11,7 +11,7 @@ import { ExtensionRecommendationNotificationServiceChannel } from '../../../../p import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts similarity index 97% rename from src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index d189fce4a3b..41978486698 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -6,7 +6,7 @@ import { localize2 } from '../../../../nls.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { URI } from '../../../../base/common/uri.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { Schemas } from '../../../../base/common/network.js'; import { Action2 } from '../../../../platform/actions/common/actions.js'; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts similarity index 98% rename from src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts index bcab2c6a34d..b1223ae226c 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts @@ -20,17 +20,17 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js'; -import { IProfileAnalysisWorkerService } from '../../../../platform/profiling/electron-sandbox/profileAnalysisWorkerService.js'; +import { IProfileAnalysisWorkerService } from '../../../../platform/profiling/electron-browser/profileAnalysisWorkerService.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { RuntimeExtensionsInput } from '../common/runtimeExtensionsInput.js'; import { createSlowExtensionAction } from './extensionsSlowActions.js'; import { IExtensionHostProfileService } from './runtimeExtensionsEditor.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js'; import { IExtensionHostProfile, IExtensionService, IResponsiveStateChangeEvent, ProfileSession } from '../../../services/extensions/common/extensions.js'; -import { ExtensionHostProfiler } from '../../../services/extensions/electron-sandbox/extensionHostProfiler.js'; +import { ExtensionHostProfiler } from '../../../services/extensions/electron-browser/extensionHostProfiler.js'; import { ITimerService } from '../../../services/timer/browser/timerService.js'; export class ExtensionsAutoProfiler implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts index 447f4c9ff1f..46120a9e645 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts @@ -16,7 +16,7 @@ import { joinPath } from '../../../../base/common/resources.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { Utils } from '../../../../platform/profiling/common/profiling.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts b/src/vs/workbench/contrib/extensions/electron-browser/remoteExtensionsInit.ts similarity index 100% rename from src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts rename to src/vs/workbench/contrib/extensions/electron-browser/remoteExtensionsInit.ts diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts similarity index 100% rename from src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts rename to src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts similarity index 100% rename from src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index a380a714200..39d0a701927 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -18,7 +18,7 @@ import { NullTelemetryService } from '../../../../../platform/telemetry/common/t import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js'; import { TestContextService, TestProductService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; -import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { TestNotificationService } from '../../../../../platform/notification/test/common/testNotificationService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -37,7 +37,7 @@ import { INotificationService, Severity, IPromptChoice, IPromptOptions } from '. import { NativeURLService } from '../../../../../platform/url/common/urlService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { ExtensionType } from '../../../../../platform/extensions/common/extensions.js'; -import { ISharedProcessService } from '../../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js'; import { FileService } from '../../../../../platform/files/common/fileService.js'; import { NullLogService, ILogService } from '../../../../../platform/log/common/log.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index bce84045b3e..b194b88322d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -26,16 +26,16 @@ import { NullTelemetryService } from '../../../../../platform/telemetry/common/t import { IExtensionService, toExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { TestContextService, TestWorkspaceTrustManagementService } from '../../../../test/common/workbenchTestServices.js'; -import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js'; import { NativeURLService } from '../../../../../platform/url/common/urlService.js'; import { URI } from '../../../../../base/common/uri.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js'; -import { RemoteAgentService } from '../../../../services/remote/electron-sandbox/remoteAgentService.js'; +import { RemoteAgentService } from '../../../../services/remote/electron-browser/remoteAgentService.js'; import { IExtensionContributions, ExtensionType, IExtensionDescription, IExtension } from '../../../../../platform/extensions/common/extensions.js'; -import { ISharedProcessService } from '../../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { ILabelService, IFormatterChangeEvent } from '../../../../../platform/label/common/label.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; @@ -45,7 +45,7 @@ import { ProgressService } from '../../../../services/progress/browser/progressS import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; import { TestEnvironmentService, TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; -import { INativeWorkbenchEnvironmentService } from '../../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../../services/environment/electron-browser/environmentService.js'; import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js'; import { IUserDataSyncEnablementService } from '../../../../../platform/userDataSync/common/userDataSync.js'; import { UserDataSyncEnablementService } from '../../../../../platform/userDataSync/common/userDataSyncEnablementService.js'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 3752be94aaa..4b5ebfc1d22 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -26,7 +26,7 @@ import { NullTelemetryService } from '../../../../../platform/telemetry/common/t import { IExtensionService, toExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { TestMenuService } from '../../../../test/browser/workbenchTestServices.js'; -import { TestSharedProcessService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js'; import { NativeURLService } from '../../../../../platform/url/common/urlService.js'; @@ -34,9 +34,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { SinonStub } from 'sinon'; import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js'; -import { RemoteAgentService } from '../../../../services/remote/electron-sandbox/remoteAgentService.js'; +import { RemoteAgentService } from '../../../../services/remote/electron-browser/remoteAgentService.js'; import { ExtensionType, IExtension } from '../../../../../platform/extensions/common/extensions.js'; -import { ISharedProcessService } from '../../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; import { IMenuService } from '../../../../../platform/actions/common/actions.js'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 6f9c1ee9667..c68caf3fbbb 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -24,7 +24,7 @@ import { IPager } from '../../../../../base/common/paging.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; -import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js'; import { IProgressService } from '../../../../../platform/progress/common/progress.js'; @@ -36,8 +36,8 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { ExtensionType } from '../../../../../platform/extensions/common/extensions.js'; import { ExtensionKind } from '../../../../../platform/environment/common/environment.js'; import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js'; -import { RemoteAgentService } from '../../../../services/remote/electron-sandbox/remoteAgentService.js'; -import { ISharedProcessService } from '../../../../../platform/ipc/electron-sandbox/services.js'; +import { RemoteAgentService } from '../../../../services/remote/electron-browser/remoteAgentService.js'; +import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js'; import { TestContextService } from '../../../../test/common/workbenchTestServices.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; diff --git a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts similarity index 99% rename from src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts rename to src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts index 572445bbec5..1edaaa8ada1 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts @@ -14,7 +14,7 @@ import { Schemas } from '../../../../base/common/network.js'; import { IConfigurationRegistry, Extensions, ConfigurationScope, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; -import { IExternalTerminalService } from '../../../../platform/externalTerminal/electron-sandbox/externalTerminalService.js'; +import { IExternalTerminalService } from '../../../../platform/externalTerminal/electron-browser/externalTerminalService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { TerminalContextKeys } from '../../terminal/common/terminalContextKey.js'; import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js'; diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index bff940f4e0b..eeea241e304 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -5,7 +5,7 @@ import { localize } from '../../../../../nls.js'; import { mark } from '../../../../../base/common/performance.js'; -import { assertIsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { IPathService } from '../../../../services/path/common/pathService.js'; import { IAction, toAction } from '../../../../../base/common/actions.js'; import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, BINARY_TEXT_FILE_MODE } from '../../common/files.js'; @@ -125,7 +125,7 @@ export class TextFileEditor extends AbstractTextCodeEditor const textFileModel = resolvedModel; // Editor - const control = assertIsDefined(this.editorControl); + const control = assertReturnsDefined(this.editorControl); control.setModel(textFileModel.textEditorModel); // Restore view state (unless provided by options) diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index ff3943dc129..468d501385d 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -19,7 +19,7 @@ import { IFilesConfiguration, SortOrder } from './files.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { ExplorerFileNestingTrie } from './explorerFileNestingTrie.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { IFilesConfigurationService } from '../../../services/filesConfiguration/common/filesConfigurationService.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; @@ -355,7 +355,7 @@ export class ExplorerItem { if (nestedItems !== undefined) { fileEntryItem.nestedChildren = []; for (const name of nestedItems.keys()) { - const child = assertIsDefined(this.children.get(name)); + const child = assertReturnsDefined(this.children.get(name)); fileEntryItem.nestedChildren.push(child); child.nestedParent = fileEntryItem; } diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts rename to src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileCommands.ts b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts similarity index 100% rename from src/vs/workbench/contrib/files/electron-sandbox/fileCommands.ts rename to src/vs/workbench/contrib/files/electron-browser/fileCommands.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 86c3252e16d..58d7cc83901 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -625,10 +625,10 @@ class KeepOrUndoSessionAction extends AbstractInline2ChatAction { icon: _keep ? Codicon.check : Codicon.discard, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, ctxHasRequestInProgress.negate()), keybinding: [{ - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.WorkbenchContrib + 10, // win over new-window-action primary: _keep - ? KeyMod.CtrlCmd | KeyCode.Enter - : KeyMod.CtrlCmd | KeyCode.Backspace + ? KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyY + : KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyN }], menu: [{ id: MENU_INLINE_CHAT_WIDGET_STATUS, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 9ae4a8b1bda..b12fb8d7b14 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -43,7 +43,8 @@ import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/edit import { IViewsService } from '../../../services/views/common/viewsService.js'; import { showChatView } from '../../chat/browser/chat.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; -import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatRequestVariableEntry, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; +import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { IChatService } from '../../chat/common/chatService.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index d78f4aef478..f68cf896d94 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -38,10 +38,9 @@ import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modes import { createStyleSheet2 } from '../../../../base/browser/domStylesheets.js'; import { stringValue } from '../../../../base/browser/cssValue.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; -import { Emitter } from '../../../../base/common/event.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; -import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../chat/common/promptSyntax/constants.js'; -import { MODE_FILE_EXTENSION } from '../../../../platform/prompts/common/prompts.js'; +import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../chat/common/promptSyntax/promptTypes.js'; +import { MODE_FILE_EXTENSION } from '../../chat/common/promptSyntax/config/promptFileLocations.js'; /** * Set of language IDs where inline chat hints should not be shown. @@ -242,7 +241,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont const ghostState = ghostCtrl?.model.read(r)?.state.read(r); const textFocus = editorObs.isTextFocused.read(r); - let position = editorObs.cursorPosition.read(r); + const position = editorObs.cursorPosition.read(r); const model = editorObs.model.read(r); const kb = keyObs.read(r); @@ -255,14 +254,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont return undefined; } - // DEBT - I cannot use `model.onDidChangeContent` directly here - // https://github.com/microsoft/vscode/issues/242059 - const emitter = r.store.add(new Emitter()); - r.store.add(model.onDidChangeContent(() => emitter.fire())); - observableFromEvent(emitter.event, () => model.getVersionId()).read(r); - - // position can be wrong - position = model.validatePosition(position); + editorObs.versionId.read(r); const visible = this._visibilityObs.read(r); const isEol = model.getLineMaxColumn(position.lineNumber) === position.column; diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChat.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts rename to src/vs/workbench/contrib/inlineChat/electron-browser/inlineChat.contribution.ts diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts similarity index 98% rename from src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts rename to src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts index 54754b0b91d..1f0e9638235 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts @@ -13,7 +13,7 @@ import { disposableTimeout } from '../../../../base/common/async.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { StartVoiceChatAction, StopListeningAction, VOICE_KEY_HOLD_THRESHOLD } from '../../chat/electron-sandbox/actions/voiceChatActions.js'; +import { StartVoiceChatAction, StopListeningAction, VOICE_KEY_HOLD_THRESHOLD } from '../../chat/electron-browser/actions/voiceChatActions.js'; import { IChatExecuteActionContext } from '../../chat/browser/actions/chatExecuteActions.js'; import { CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; import { HasSpeechProvider, ISpeechService } from '../../speech/common/speechService.js'; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 13eaefd9ace..21f8f4dc52a 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -67,7 +67,7 @@ import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../../../ch import { ChatAgentLocation, ChatMode } from '../../../chat/common/constants.js'; import { ILanguageModelsService, LanguageModelsService } from '../../../chat/common/languageModels.js'; import { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js'; -import { IPromptsService } from '../../../chat/common/promptSyntax/service/types.js'; +import { IPromptsService } from '../../../chat/common/promptSyntax/service/promptsService.js'; import { MockChatModeService } from '../../../chat/test/common/mockChatModeService.js'; import { MockLanguageModelToolsService } from '../../../chat/test/common/mockLanguageModelToolsService.js'; import { INotebookEditorService } from '../../../notebook/browser/services/notebookEditorService.js'; @@ -224,6 +224,7 @@ suite('InlineChatController', function () { inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService)); store.add(instaService.get(ILanguageModelsService) as LanguageModelsService); + store.add(instaService.get(IEditorWorkerService) as TestWorkerService); store.add(instaService.createInstance(ChatInputBoxContentProvider)); @@ -666,7 +667,8 @@ suite('InlineChatController', function () { await r; }); - test('Clicking "re-run without /doc" while a request is in progress closes the widget #5997', async function () { + // TODO@jrieken https://github.com/microsoft/vscode/issues/251429 + test.skip('Clicking "re-run without /doc" while a request is in progress closes the widget #5997', async function () { model.setValue(''); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index e9bbdebabd9..ac49a111cd3 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -155,6 +155,7 @@ suite('InlineChatSession', function () { }); + store.add(instaService.get(IEditorWorkerService) as TestWorkerService); model = store.add(instaService.get(IModelService).createModel('one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven', null)); editor = store.add(instantiateTestCodeEditor(instaService, model)); }); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts b/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts index edfa6e50429..2a6ea9759da 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts @@ -14,21 +14,31 @@ import { LineRange } from '../../../../../editor/common/core/ranges/lineRange.js import { MovedText } from '../../../../../editor/common/diff/linesDiffComputer.js'; import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from '../../../../../editor/common/diff/rangeMapping.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; +import { disposableTimeout } from '../../../../../base/common/async.js'; +import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; -export class TestWorkerService extends mock() { +export class TestWorkerService extends mock() implements IDisposable { - private readonly _worker = new EditorWorker(); + private readonly _store = new DisposableStore(); + private readonly _worker = this._store.add(new EditorWorker()); constructor(@IModelService private readonly _modelService: IModelService) { super(); } + dispose(): void { + this._store.dispose(); + } override async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined, pretty?: boolean | undefined): Promise { return undefined; } override async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { + await new Promise(resolve => disposableTimeout(() => resolve(), 0, this._store)); + if (this._store.isDisposed) { + return null; + } const originalModel = this._modelService.getModel(original); const modifiedModel = this._modelService.getModel(modified); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 47fb9d3d0d2..c6dd6f89ab4 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -113,7 +113,11 @@ export class InteractiveDocumentContribution extends Disposable implements IWork }, { createEditorInput: ({ resource }) => { - const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.inputResource.toString() === resource.toString()); + const editorInput = editorService.findEditors({ + resource, + editorId: 'interactive', + typeId: InteractiveEditorInput.ID + }, { order: EditorsOrder.SEQUENTIAL }).at(0); return editorInput!; } } diff --git a/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts b/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts index dec54b24f4b..de429e98bcf 100644 --- a/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts +++ b/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts @@ -27,6 +27,7 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi private domNode: HTMLElement | undefined; private ariaLabel: string = ''; + private label: KeybindingLabel | undefined; constructor( private readonly editor: ICodeEditor, @@ -111,10 +112,13 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi hintElement.appendChild(before); - const label = new KeybindingLabel(hintElement, OS); - label.set(keybinding); - label.element.style.width = 'min-content'; - label.element.style.display = 'inline'; + if (this.label) { + this.label.dispose(); + } + this.label = this._register(new KeybindingLabel(hintElement, OS)); + this.label.set(keybinding); + this.label.element.style.width = 'min-content'; + this.label.element.style.display = 'inline'; hintElement.appendChild(after); this.domNode.append(hintElement); @@ -161,5 +165,6 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi override dispose(): void { super.dispose(); this.editor.removeContentWidget(this); + this.label?.dispose(); } } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts rename to src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueReporterService.ts similarity index 99% rename from src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts rename to src/vs/workbench/contrib/issue/electron-browser/issueReporterService.ts index cbdf9723c31..4ad8cca1d8a 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueReporterService.ts @@ -17,7 +17,7 @@ import { INativeHostService } from '../../../../platform/native/common/native.js import { IProcessService } from '../../../../platform/process/common/process.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IUpdateService, StateType } from '../../../../platform/update/common/update.js'; -import { applyZoom } from '../../../../platform/window/electron-sandbox/window.js'; +import { applyZoom } from '../../../../platform/window/electron-browser/window.js'; import { BaseIssueReporterService } from '../browser/baseIssueReporterService.js'; import { IssueReporterData as IssueReporterModelData } from '../browser/issueReporterModel.js'; import { IIssueFormService, IssueReporterData, IssueType } from '../common/issue.js'; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts similarity index 100% rename from src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts rename to src/vs/workbench/contrib/issue/electron-browser/issueService.ts diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts b/src/vs/workbench/contrib/issue/electron-browser/nativeIssueFormService.ts similarity index 100% rename from src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts rename to src/vs/workbench/contrib/issue/electron-browser/nativeIssueFormService.ts diff --git a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution.ts b/src/vs/workbench/contrib/localHistory/electron-browser/localHistory.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution.ts rename to src/vs/workbench/contrib/localHistory/electron-browser/localHistory.contribution.ts diff --git a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/electron-browser/localHistoryCommands.ts similarity index 100% rename from src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts rename to src/vs/workbench/contrib/localHistory/electron-browser/localHistoryCommands.ts diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts b/src/vs/workbench/contrib/localization/electron-browser/localization.contribution.ts similarity index 98% rename from src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts rename to src/vs/workbench/contrib/localization/electron-browser/localization.contribution.ts index 4201a6563f1..0455b7556b4 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/electron-browser/localization.contribution.ts @@ -139,7 +139,7 @@ class NativeLocalizationWorkbenchContribution extends BaseLocalizationWorkbenchC const loc = manifest?.contributes?.localizations?.find(x => locale.startsWith(x.languageId.toLowerCase())); const languageName = loc ? (loc.languageName || locale) : locale; const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale; - const translationsFromPack: { [key: string]: string } = translation?.contents?.['vs/workbench/contrib/localization/electron-sandbox/minimalTranslations'] ?? {}; + const translationsFromPack: { [key: string]: string } = translation?.contents?.['vs/workbench/contrib/localization/electron-browser/minimalTranslations'] ?? {}; const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'; const useEnglish = !translationsFromPack[promptMessageKey]; diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/minimalTranslations.ts b/src/vs/workbench/contrib/localization/electron-browser/minimalTranslations.ts similarity index 100% rename from src/vs/workbench/contrib/localization/electron-sandbox/minimalTranslations.ts rename to src/vs/workbench/contrib/localization/electron-browser/minimalTranslations.ts diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts rename to src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts similarity index 97% rename from src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts rename to src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index bc7e00198c2..2e0f77cc3d7 100644 --- a/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -6,7 +6,7 @@ import { Action } from '../../../../base/common/actions.js'; import * as nls from '../../../../nls.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { joinPath } from '../../../../base/common/resources.js'; import { Schemas } from '../../../../base/common/network.js'; diff --git a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts index 83bff8daa8c..f49818accba 100644 --- a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts +++ b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts @@ -14,8 +14,8 @@ import { ILabelService } from '../../../../platform/label/common/label.js'; import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; import { IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService } from '../../chat/browser/chatContextPickService.js'; -import { IDiagnosticVariableEntryFilterData } from '../../chat/common/chatModel.js'; +import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextPicker } from '../../chat/browser/chatContextPickService.js'; +import { IDiagnosticVariableEntryFilterData } from '../../chat/common/chatVariableEntries.js'; class MarkerChatContextPick implements IChatContextPickerItem { @@ -29,7 +29,7 @@ class MarkerChatContextPick implements IChatContextPickerItem { @ILabelService private readonly _labelService: ILabelService, ) { } - asPicker(): { readonly placeholder: string; readonly picks: Promise<(IChatContextPickerPickItem | IQuickPickSeparator)[]> } { + asPicker(): IChatContextPicker { const markers = this._markerService.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); const grouped = groupBy(markers, (a, b) => extUri.compare(a.resource, b.resource)); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index abf4f085d66..01a3ca06f6d 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -5,7 +5,6 @@ import { h } from '../../../../base/browser/dom.js'; import { assertNever } from '../../../../base/common/assert.js'; -import { raceTimeout } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { groupBy } from '../../../../base/common/collections.js'; import { Event } from '../../../../base/common/event.js'; @@ -34,9 +33,9 @@ import { ActiveEditorContext, ResourceContextKey } from '../../../common/context import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { ChatViewId, IChatWidget, IChatWidgetService } from '../../chat/browser/chat.js'; +import { IChatWidgetService } from '../../chat/browser/chat.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ChatAgentLocation, ChatMode } from '../../chat/common/constants.js'; +import { ChatMode } from '../../chat/common/constants.js'; import { ILanguageModelsService } from '../../chat/common/languageModels.js'; import { extensionsFilterSubMenu, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; @@ -47,6 +46,7 @@ import { IMcpSamplingService, IMcpServer, IMcpServerStartOpts, IMcpService, IMcp import { McpAddConfigurationCommand } from './mcpCommandsAddConfiguration.js'; import { McpResourceQuickAccess, McpResourceQuickPick } from './mcpResourceQuickAccess.js'; import { McpUrlHandler } from './mcpUrlHandler.js'; +import { openPanelChatAndGetWidget } from './openPanelChatAndGetWidget.js'; // acroynms do not get localized const category: ILocalizedString = { @@ -135,7 +135,7 @@ export class ListMcpServerCommand extends Action2 { if (!picked) { // no-op } else if (picked.id === '$add') { - commandService.executeCommand(AddConfigurationAction.ID); + commandService.executeCommand(McpCommandIds.AddConfiguration); } else { commandService.executeCommand(McpCommandIds.ServerOptions, picked.id); } @@ -454,11 +454,9 @@ export class ResetMcpCachedTools extends Action2 { } export class AddConfigurationAction extends Action2 { - static readonly ID = 'workbench.mcp.addConfiguration'; - constructor() { super({ - id: AddConfigurationAction.ID, + id: McpCommandIds.AddConfiguration, title: localize2('mcp.addConfiguration', "Add Server..."), metadata: { description: localize2('mcp.addConfiguration.description', "Installs a new Model Context protocol to the mcp.json settings"), @@ -747,18 +745,4 @@ export class McpStartPromptingServerCommand extends Action2 { } } -export async function openPanelChatAndGetWidget(viewsService: IViewsService, chatService: IChatWidgetService): Promise { - await viewsService.openView(ChatViewId, true); - const widgets = chatService.getWidgetsByLocations(ChatAgentLocation.Panel); - if (widgets.length) { - return widgets[0]; - } - const eventPromise = Event.toPromise(Event.filter(chatService.onDidAddWidget, e => e.location === ChatAgentLocation.Panel)); - - return await raceTimeout( - eventPromise, - 10_000, // should be enough time for chat to initialize... - () => eventPromise.cancel(), - ); -} diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts index 781802ec8c3..9e09d86f0a2 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts @@ -24,7 +24,6 @@ import { INotificationService } from '../../../../platform/notification/common/n import { IQuickInputService, IQuickPickItem, QuickPickInput } from '../../../../platform/quickinput/common/quickInput.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { EditorsOrder } from '../../../common/editor.js'; import { IJSONEditingService } from '../../../services/configuration/common/jsonEditing.js'; import { ConfiguredInput } from '../../../services/configurationResolver/common/configurationResolver.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; @@ -489,8 +488,7 @@ export class McpAddConfigurationCommand { } const pick = await this._quickInputService.pick(items, { placeHolder, ignoreFocusLost: true }); - const getEditors = () => this._editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE) - .filter(e => e.editor.resource?.toString() === resource.toString()); + const getEditors = () => this._editorService.findEditors(resource); switch (pick?.id) { case 'show': diff --git a/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts b/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts index de7a0805a19..8d57bf010a0 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts @@ -82,7 +82,7 @@ export class McpPromptArgumentPick extends Disposable { quickPick.activeItems = restore?.activeItems ?? []; quickPick.buttons = i > 0 ? [this._quickInputService.backButton] : []; - const value = await this._getArg(arg, !!restore, token); + const value = await this._getArg(arg, !!restore, args, token); if (value.type === 'back') { i -= 2; } else if (value.type === 'cancel') { @@ -102,7 +102,7 @@ export class McpPromptArgumentPick extends Disposable { return args; } - private async _getArg(arg: MCP.PromptArgument, didRestoreState: boolean, token?: CancellationToken): Promise { + private async _getArg(arg: MCP.PromptArgument, didRestoreState: boolean, argsSoFar: Record, token?: CancellationToken): Promise { const { quickPick } = this; const store = new DisposableStore(); @@ -110,7 +110,7 @@ export class McpPromptArgumentPick extends Disposable { const asyncPicks = [ { name: localize('mcp.arg.suggestions', 'Suggestions'), - observer: this._promptCompletions(arg, input$), + observer: this._promptCompletions(arg, input$, argsSoFar), }, { name: localize('mcp.arg.files', 'Files'), @@ -218,9 +218,16 @@ export class McpPromptArgumentPick extends Disposable { } } - private _promptCompletions(arg: MCP.PromptArgument, input: IObservable) { + private _promptCompletions(arg: MCP.PromptArgument, input: IObservable, argsSoFar: Record) { + const alreadyResolved: Record = {}; + for (const [key, value] of Object.entries(argsSoFar)) { + if (value) { + alreadyResolved[key] = value; + } + } + return this._asyncCompletions(input, async (i, t) => { - const items = await this.prompt.complete(arg.name, i, t); + const items = await this.prompt.complete(arg.name, i, alreadyResolved, t); return items.map((i): PickItem => ({ id: `suggest:${i}`, label: i, action: 'suggest' })); }); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts index 2946ced9d69..84f38319c57 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts @@ -23,10 +23,10 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatWidgetService } from '../../chat/browser/chat.js'; import { resolveImageEditorAttachContext } from '../../chat/browser/chatAttachmentResolve.js'; -import { IChatRequestVariableEntry } from '../../chat/common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { IMcpResource, IMcpResourceTemplate, IMcpServer, IMcpService, isMcpResourceTemplate, McpCapability, McpConnectionState, McpResourceURI } from '../common/mcpTypes.js'; import { IUriTemplateVariable } from '../common/uriTemplate.js'; -import { openPanelChatAndGetWidget } from './mcpCommands.js'; +import { openPanelChatAndGetWidget } from './openPanelChatAndGetWidget.js'; export class McpResourcePickHelper { public static sep(server: IMcpServer): IQuickPickSeparator { @@ -329,6 +329,7 @@ export class McpResourcePickHelper { rec.templates.complete([]); rec.resources.complete([]); } + publish(); })).finally(() => { store.dispose(); }); diff --git a/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts b/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts new file mode 100644 index 00000000000..96b7562386e --- /dev/null +++ b/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { raceTimeout } from '../../../../base/common/async.js'; +import { Event } from '../../../../base/common/event.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IChatWidgetService, IChatWidget, ChatViewId } from '../../chat/browser/chat.js'; +import { ChatAgentLocation } from '../../chat/common/constants.js'; + + +export async function openPanelChatAndGetWidget(viewsService: IViewsService, chatService: IChatWidgetService): Promise { + await viewsService.openView(ChatViewId, true); + const widgets = chatService.getWidgetsByLocations(ChatAgentLocation.Panel); + if (widgets.length) { + return widgets[0]; + } + + const eventPromise = Event.toPromise(Event.filter(chatService.onDidAddWidget, e => e.location === ChatAgentLocation.Panel)); + + return await raceTimeout( + eventPromise, + 10000, // should be enough time for chat to initialize... + () => eventPromise.cancel() + ); +} diff --git a/src/vs/workbench/contrib/mcp/common/discovery/configMcpDiscovery.ts b/src/vs/workbench/contrib/mcp/common/discovery/configMcpDiscovery.ts index db0dd5ee693..0414c564e70 100644 --- a/src/vs/workbench/contrib/mcp/common/discovery/configMcpDiscovery.ts +++ b/src/vs/workbench/contrib/mcp/common/discovery/configMcpDiscovery.ts @@ -7,11 +7,13 @@ import { equals as arrayEquals } from '../../../../../base/common/arrays.js'; import { Throttler } from '../../../../../base/common/async.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { autorunDelta, ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; -import { isAbsolute, join } from '../../../../../base/common/path.js'; +import { posix as pathPosix, win32 as pathWin32, sep as pathSep } from '../../../../../base/common/path.js'; +import { isWindows, OperatingSystem } from '../../../../../base/common/platform.js'; import { URI } from '../../../../../base/common/uri.js'; import { Location } from '../../../../../editor/common/languages.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js'; import { getMcpServerMapping } from '../mcpConfigFileUtils.js'; import { IMcpConfigPath, IMcpConfigPathsService } from '../mcpConfigPathsService.js'; import { IMcpConfiguration, mcpConfigurationSection } from '../mcpConfiguration.js'; @@ -37,6 +39,7 @@ export class ConfigMcpDiscovery extends Disposable implements IMcpDiscovery { @IMcpRegistry private readonly _mcpRegistry: IMcpRegistry, @ITextModelService private readonly _textModelService: ITextModelService, @IMcpConfigPathsService private readonly _mcpConfigPathsService: IMcpConfigPathsService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, ) { super(); } @@ -100,6 +103,7 @@ export class ConfigMcpDiscovery extends Disposable implements IMcpDiscovery { const uri = src.path.uri; return uri && src.getServerToLocationMapping(uri); })); + const remoteEnv = await this._remoteAgentService.getEnvironment(); for (const [index, src] of this.configSources.entries()) { const collectionId = `mcp.config.${src.path.id}`; @@ -116,6 +120,14 @@ export class ConfigMcpDiscovery extends Disposable implements IMcpDiscovery { } const configMapping = configMappings[index]; + const { isAbsolute, join, sep } = src.path.remoteAuthority && remoteEnv + ? (remoteEnv.os === OperatingSystem.Windows ? pathWin32 : pathPosix) + : (isWindows ? pathWin32 : pathPosix); + const fsPathForRemote = (uri: URI) => { + const fsPathLocal = uri.fsPath; + return fsPathLocal.replaceAll(pathSep, sep); + }; + const nextDefinitions = Object.entries(value?.servers || {}).map(([name, value]): McpServerDefinition => ({ id: `${collectionId}.${name}`, label: name, @@ -132,8 +144,12 @@ export class ConfigMcpDiscovery extends Disposable implements IMcpDiscovery { cwd: value.cwd // if the cwd is defined in a workspace folder but not absolute (and not // a variable or tilde-expansion) then resolve it in the workspace folder - ? (!isAbsolute(value.cwd) && !value.cwd.startsWith('~') && !value.cwd.startsWith('${') && src.path.workspaceFolder ? join(src.path.workspaceFolder.uri.fsPath, value.cwd) : value.cwd) - : src.path.workspaceFolder?.uri.fsPath, + ? (!isAbsolute(value.cwd) && !value.cwd.startsWith('~') && !value.cwd.startsWith('${') && src.path.workspaceFolder + ? join(fsPathForRemote(src.path.workspaceFolder.uri), value.cwd) + : value.cwd) + : src.path.workspaceFolder + ? fsPathForRemote(src.path.workspaceFolder.uri) + : undefined, }, roots: src.path.workspaceFolder ? [src.path.workspaceFolder.uri] : [], variableReplacement: { diff --git a/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts b/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts index 36151a9565d..caed5bff99d 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts @@ -68,6 +68,10 @@ export class McpSamplingService extends Disposable implements IMcpSamplingServic }; }).filter(isDefined); + if (opts.params.systemPrompt) { + messages.unshift({ role: ChatMessageRole.System, content: [{ type: 'text', value: opts.params.systemPrompt }] }); + } + const model = await this._getMatchingModel(opts); // todo@connor4312: nullExtensionDescription.identifier -> undefined with API update const response = await this._languageModelsService.sendChatRequest(model, new ExtensionIdentifier('Github.copilot-chat'), messages, {}, token); diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index 291c9e8fcf9..4a5da51d7b4 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -9,7 +9,7 @@ import * as json from '../../../../base/common/json.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { LRUCache } from '../../../../base/common/map.js'; import { mapValues } from '../../../../base/common/objects.js'; -import { autorun, autorunWithStore, derived, disposableObservableValue, IDerivedReader, IObservable, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js'; +import { autorun, derived, disposableObservableValue, IDerivedReader, IObservable, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -19,6 +19,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ILogger, ILoggerService } from '../../../../platform/log/common/log.js'; import { INotificationService, IPromptChoice, Severity } from '../../../../platform/notification/common/notification.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; @@ -312,6 +313,7 @@ export class McpServer extends Disposable implements IMcpServer { @INotificationService private readonly _notificationService: INotificationService, @IOpenerService private readonly _openerService: IOpenerService, @IMcpSamplingService private readonly _samplingService: IMcpSamplingService, + @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, ) { super(); @@ -335,24 +337,37 @@ export class McpServer extends Disposable implements IMcpServer { () => workspacesService.getWorkspace().folders, ); - this._register(autorunWithStore(reader => { + const workspacesWithCanonicalURIs = derived(reader => { + const folders = workspaces.read(reader); + return new ObservablePromise((async () => { + let uris = folders.map(f => f.uri); + try { + uris = await Promise.all(uris.map(u => this._remoteAuthorityResolverService.getCanonicalURI(u))); + } catch (error) { + this._logger.error(`Failed to resolve workspace folder URIs: ${error}`); + } + return uris.map((uri, i): MCP.Root => ({ uri: uri.toString(), name: folders[i].name })); + })()); + }).recomputeInitiallyAndOnChange(this._store); + + this._register(autorun(reader => { const cnx = this._connection.read(reader)?.handler.read(reader); if (!cnx) { return; } - cnx.roots = workspaces.read(reader).map(wf => ({ - uri: wf.uri.toString(), - name: wf.name, - })); + const roots = workspacesWithCanonicalURIs.read(reader).promiseResult.read(reader); + if (roots?.data) { + cnx.roots = roots.data; + } })); // 2. Populate this.tools when we connect to a server. - this._register(autorunWithStore((reader, store) => { + this._register(autorun(reader => { const cnx = this._connection.read(reader); const handler = cnx?.handler.read(reader); if (handler) { - this.populateLiveData(handler, cnx?.definition.cacheNonce, store); + this.populateLiveData(handler, cnx?.definition.cacheNonce, reader.store); } else if (this._tools) { this.resetLiveData(); } @@ -668,10 +683,11 @@ class McpPrompt implements IMcpPrompt { return result.messages; } - async complete(argument: string, prefix: string, token?: CancellationToken): Promise { + async complete(argument: string, prefix: string, alreadyResolved: Record, token?: CancellationToken): Promise { const result = await McpServer.callOn(this._server, h => h.complete({ ref: { type: 'ref/prompt', name: this._definition.name }, - argument: { name: argument, value: prefix, } + argument: { name: argument, value: prefix }, + context: { arguments: alreadyResolved }, }, token), token); return result.completion.values; } @@ -849,7 +865,9 @@ class McpResourceTemplate implements IMcpResourceTemplate { const result = await McpServer.callOn(this._server, h => h.complete({ ref: { type: 'ref/resource', uri: this._definition.uriTemplate }, argument: { name: templatePart, value: prefix }, - resolved: mapValues(alreadyResolved, v => Array.isArray(v) ? v.join('/') : v), + context: { + arguments: mapValues(alreadyResolved, v => Array.isArray(v) ? v.join('/') : v), + }, }, token), token); return result.completion.values; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts b/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts index aea97239ad0..8ac57508ac9 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts @@ -521,7 +521,7 @@ export class McpServerRequestHandler extends Disposable { /** * Find completions for an argument */ - complete(params: MCP.CompleteRequest2['params'], token?: CancellationToken): Promise { - return this.sendRequest({ method: 'completion/complete', params }, token); + complete(params: MCP.CompleteRequest['params'], token?: CancellationToken): Promise { + return this.sendRequest({ method: 'completion/complete', params }, token); } } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 5363b76ee00..69c2a62680d 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -330,7 +330,7 @@ export interface IMcpPrompt { readonly arguments: readonly MCP.PromptArgument[]; /** Gets string completions for the given prompt part. */ - complete(argument: string, prefix: string, token: CancellationToken): Promise; + complete(argument: string, prefix: string, alreadyResolved: Record, token: CancellationToken): Promise; resolve(args: Record, token?: CancellationToken): Promise; } @@ -609,6 +609,10 @@ export const mcpServerIcon = registerIcon('mcp-server', Codicon.mcp, localize('m export namespace McpResourceURI { export const scheme = 'mcp-resource'; + // Random placeholder for empty authorities, otherwise they're represente as + // `scheme//path/here` in the URI which would get normalized to `scheme/path/here`. + const emptyAuthorityPlaceholder = 'dylo78gyp'; // chosen by a fair dice roll. Guaranteed to be random. + export function fromServer(def: McpDefinitionReference, resourceURI: URI | string): URI { if (typeof resourceURI === 'string') { resourceURI = URI.parse(resourceURI); @@ -616,7 +620,7 @@ export namespace McpResourceURI { return resourceURI.with({ scheme, authority: encodeHex(VSBuffer.fromString(def.id)), - path: ['', resourceURI.scheme, resourceURI.authority].join('/') + resourceURI.path, + path: ['', resourceURI.scheme, resourceURI.authority || emptyAuthorityPlaceholder].join('/') + resourceURI.path, }); } @@ -636,8 +640,8 @@ export namespace McpResourceURI { definitionId: decodeHex(uri.authority).toString(), resourceURI: uri.with({ scheme: serverScheme, - authority, - path: '/' + path.join('/'), + authority: authority.toLowerCase() === emptyAuthorityPlaceholder ? '' : authority, + path: path.length ? ('/' + path.join('/')) : '', }), }; } diff --git a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts index 03e6a47b8c6..2e4a4974794 100644 --- a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts +++ b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts @@ -14,16 +14,8 @@ */ export namespace MCP { - // https://github.com/modelcontextprotocol/modelcontextprotocol/pull/598 - export interface CompleteRequest2 extends MCP.CompleteRequest { - params: MCP.CompleteRequest['params'] & { - /** - * Previously-resolved variables in a URI template. The keys of the object - * are be the template's variable expressions including surrounding braces. - */ - resolved?: { [key: string]: string }; - }; - } + // Nothing, yet + } //#endregion @@ -1144,6 +1136,16 @@ export namespace MCP { */ value: string; }; + + /** + * Additional, optional context for completions + */ + context?: { + /** + * Previously-resolved variables in a URI template or prompt. + */ + arguments?: { [key: string]: string }; + }; }; } diff --git a/src/vs/workbench/contrib/mcp/electron-sandbox/mcp.contribution.ts b/src/vs/workbench/contrib/mcp/electron-browser/mcp.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/mcp/electron-sandbox/mcp.contribution.ts rename to src/vs/workbench/contrib/mcp/electron-browser/mcp.contribution.ts diff --git a/src/vs/workbench/contrib/mcp/electron-sandbox/mcpDevModeDebuggingNode.ts b/src/vs/workbench/contrib/mcp/electron-browser/mcpDevModeDebuggingNode.ts similarity index 100% rename from src/vs/workbench/contrib/mcp/electron-sandbox/mcpDevModeDebuggingNode.ts rename to src/vs/workbench/contrib/mcp/electron-browser/mcpDevModeDebuggingNode.ts diff --git a/src/vs/workbench/contrib/mcp/electron-sandbox/nativeMpcDiscovery.ts b/src/vs/workbench/contrib/mcp/electron-browser/nativeMpcDiscovery.ts similarity index 100% rename from src/vs/workbench/contrib/mcp/electron-sandbox/nativeMpcDiscovery.ts rename to src/vs/workbench/contrib/mcp/electron-browser/nativeMpcDiscovery.ts diff --git a/src/vs/workbench/contrib/mcp/node/mcpStdioStateHandler.ts b/src/vs/workbench/contrib/mcp/node/mcpStdioStateHandler.ts new file mode 100644 index 00000000000..3166cdea8c5 --- /dev/null +++ b/src/vs/workbench/contrib/mcp/node/mcpStdioStateHandler.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ChildProcessWithoutNullStreams } from 'child_process'; +import { TimeoutTimer } from '../../../../base/common/async.js'; +import { IDisposable } from '../../../../base/common/lifecycle.js'; +import { killTree } from '../../../../base/node/processes.js'; +import { isWindows } from '../../../../base/common/platform.js'; + +const enum McpProcessState { + Running, + StdinEnded, + KilledPolite, + KilledForceful, +} + +/** + * Manages graceful shutdown of MCP stdio connections following the MCP specification. + * + * Per spec, shutdown should: + * 1. Close the input stream to the child process + * 2. Wait for the server to exit, or send SIGTERM if it doesn't exit within 10 seconds + * 3. Send SIGKILL if the server doesn't exit within 10 seconds after SIGTERM + * 4. Allow forceful killing if called twice + */ +export class McpStdioStateHandler implements IDisposable { + private static readonly GRACE_TIME_MS = 10_000; + + private _procState = McpProcessState.Running; + private _nextTimeout?: IDisposable; + + public get stopped() { + return this._procState !== McpProcessState.Running; + } + + constructor( + private readonly _child: ChildProcessWithoutNullStreams, + private readonly _graceTimeMs: number = McpStdioStateHandler.GRACE_TIME_MS + ) { } + + /** + * Initiates graceful shutdown. If called while shutdown is already in progress, + * forces immediate termination. + */ + public stop(): void { + if (this._procState === McpProcessState.Running) { + try { + this._child.stdin.end(); + } catch (error) { + // If stdin.end() fails, continue with termination sequence + // This can happen if the stream is already in an error state + } + this._nextTimeout = new TimeoutTimer(() => this.killPolite(), this._graceTimeMs); + } else { + this._nextTimeout?.dispose(); + this.killForceful(); + } + } + + private async killPolite() { + this._procState = McpProcessState.KilledPolite; + this._nextTimeout = new TimeoutTimer(() => this.killForceful(), this._graceTimeMs); + + if (this._child.pid) { + if (!isWindows) { + await killTree(this._child.pid, false).catch(() => { + this._child.kill('SIGTERM'); + }); + } + } else { + this._child.kill('SIGTERM'); + } + } + + private async killForceful() { + this._procState = McpProcessState.KilledForceful; + + if (this._child.pid) { + await killTree(this._child.pid, true).catch(() => { + this._child.kill('SIGKILL'); + }); + } else { + this._child.kill(); + } + } + + public write(message: string): void { + if (!this.stopped) { + this._child.stdin.write(message + '\n'); + } + } + + public dispose() { + this._nextTimeout?.dispose(); + } +} diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpTypes.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpTypes.test.ts new file mode 100644 index 00000000000..ca832e81c44 --- /dev/null +++ b/src/vs/workbench/contrib/mcp/test/common/mcpTypes.test.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { McpResourceURI } from '../../common/mcpTypes.js'; +import * as assert from 'assert'; + +suite('MCP Types', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('McpResourceURI - round trips', () => { + const roundTrip = (uri: string) => { + const from = McpResourceURI.fromServer({ label: '', id: 'my-id' }, uri); + const to = McpResourceURI.toServer(from); + assert.strictEqual(to.definitionId, 'my-id'); + assert.strictEqual(to.resourceURI.toString(true), uri, `expected to round trip ${uri}`); + }; + + roundTrip('file:///path/to/file.txt'); + roundTrip('custom-scheme://my-path/to/resource.txt'); + roundTrip('custom-scheme://my-path'); + roundTrip('custom-scheme://my-path/'); + roundTrip('custom-scheme://my-path/?with=query¶ms=here'); + }); +}); diff --git a/src/vs/workbench/contrib/mcp/test/node/mcpStdioStateHandler.test.ts b/src/vs/workbench/contrib/mcp/test/node/mcpStdioStateHandler.test.ts new file mode 100644 index 00000000000..9bee4963b01 --- /dev/null +++ b/src/vs/workbench/contrib/mcp/test/node/mcpStdioStateHandler.test.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawn } from 'child_process'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import * as assert from 'assert'; +import { McpStdioStateHandler } from '../../node/mcpStdioStateHandler.js'; +import { isWindows } from '../../../../../base/common/platform.js'; + +const GRACE_TIME = 100; + +suite('McpStdioStateHandler', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + function run(code: string) { + const child = spawn('node', ['-e', code], { + stdio: 'pipe', + env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, + }); + + return { + child, + handler: store.add(new McpStdioStateHandler(child, GRACE_TIME)), + processId: new Promise((resolve) => { + child.on('spawn', () => resolve(child.pid!)); + }), + output: new Promise((resolve) => { + let output = ''; + child.stderr.setEncoding('utf-8').on('data', (data) => { + output += data.toString(); + }); + child.stdout.setEncoding('utf-8').on('data', (data) => { + output += data.toString(); + }); + child.on('close', () => resolve(output)); + }), + }; + } + + test('stdin ends process', async () => { + const { child, handler, output } = run(` + const data = require('fs').readFileSync(0, 'utf-8'); + process.stdout.write('Data received: ' + data); + process.on('SIGTERM', () => process.stdout.write('SIGTERM received')); + `); + + child.stdin.write('Hello MCP!'); + handler.stop(); + const result = await output; + assert.strictEqual(result.trim(), 'Data received: Hello MCP!'); + }); + + if (!isWindows) { + test('sigterm after grace', async () => { + const { handler, output } = run(` + setInterval(() => {}, 1000); + process.stdin.on('end', () => process.stdout.write('stdin ended\\n')); + process.stdin.resume(); + process.on('SIGTERM', () => { + process.stdout.write('SIGTERM received', () => process.exit(0)); + }); + `); + + const before = Date.now(); + handler.stop(); + const result = await output; + const delay = Date.now() - before; + assert.strictEqual(result.trim(), 'stdin ended\nSIGTERM received'); + assert.ok(delay >= GRACE_TIME, `Expected at least ${GRACE_TIME}ms delay, got ${delay}ms`); + }); + } + + test('sigkill after grace', async () => { + const { handler, output } = run(` + setInterval(() => {}, 1000); + process.stdin.on('end', () => process.stdout.write('stdin ended\\n')); + process.stdin.resume(); + process.on('SIGTERM', () => { + process.stdout.write('SIGTERM received'); + }); + `); + + const before = Date.now(); + handler.stop(); + const result = await output; + const delay = Date.now() - before; + if (!isWindows) { + assert.strictEqual(result.trim(), 'stdin ended\nSIGTERM received'); + } else { + assert.strictEqual(result.trim(), 'stdin ended'); + } + assert.ok(delay >= GRACE_TIME * 2, `Expected at least ${GRACE_TIME * 2}ms delay, got ${delay}ms`); + }); +}); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts index e6a51aed0c0..9cb565cdd61 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts @@ -76,6 +76,10 @@ export class MergeDiffComputer implements IMergeDiffComputer { } assertFn(() => { + /* + // This does not hold (see https://github.com/microsoft/vscode-copilot/issues/10610) + // TODO@hediet the diff algorithm should just use compute a string edit that transforms the input to the output, nothing else + for (const c of changes) { const inputRange = c.inputRange; const outputRange = c.outputRange; @@ -105,7 +109,7 @@ export class MergeDiffComputer implements IMergeDiffComputer { return false; } } - } + }*/ return changes.length === 0 || (changes[0].inputRange.startLineNumber === changes[0].outputRange.startLineNumber && checkAdjacentItems(changes, diff --git a/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/electron-browser/devCommands.ts similarity index 100% rename from src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts rename to src/vs/workbench/contrib/mergeEditor/electron-browser/devCommands.ts diff --git a/src/vs/workbench/contrib/mergeEditor/electron-sandbox/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/electron-browser/mergeEditor.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/mergeEditor/electron-sandbox/mergeEditor.contribution.ts rename to src/vs/workbench/contrib/mergeEditor/electron-browser/mergeEditor.contribution.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts index 69f25f754e6..c67c5f4ad7a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chat/notebookChatUtils.ts @@ -7,7 +7,7 @@ import { normalizeDriveLetter } from '../../../../../../base/common/labels.js'; import { basenameOrAuthority } from '../../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { localize } from '../../../../../../nls.js'; -import { INotebookOutputVariableEntry } from '../../../../chat/common/chatModel.js'; +import { INotebookOutputVariableEntry } from '../../../../chat/common/chatVariableEntries.js'; import { CellUri } from '../../../common/notebookCommon.js'; import { ICellOutputViewModel, INotebookEditor } from '../../notebookBrowser.js'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index bb0c49f4bba..546efce7200 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -364,7 +364,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi return; } - this._matchesCount.style.width = MAX_MATCHES_COUNT_WIDTH + 'px'; + this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px'; this._matchesCount.title = ''; // remove previous content diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts b/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts index 9a0390c9a41..571248d12e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts @@ -96,6 +96,7 @@ export class NotebookMultiCursorController extends Disposable implements INotebo position: Position; } | undefined; private trackedCells: TrackedCell[]; + private totalMatchesCount: number; private readonly _onDidChangeAnchorCell; readonly onDidChangeAnchorCell: Event; @@ -125,6 +126,7 @@ export class NotebookMultiCursorController extends Disposable implements INotebo super(); this.word = ''; this.trackedCells = []; + this.totalMatchesCount = 0; this._onDidChangeAnchorCell = this._register(new Emitter()); this.onDidChangeAnchorCell = this._onDidChangeAnchorCell.event; this.anchorDisposables = this._register(new DisposableStore()); @@ -478,6 +480,7 @@ export class NotebookMultiCursorController extends Disposable implements INotebo this.cursorsDisposables.clear(); this.cursorsControllers.clear(); this.trackedCells = []; + this.totalMatchesCount = 0; this.startPosition = undefined; this.word = ''; } @@ -496,6 +499,13 @@ export class NotebookMultiCursorController extends Disposable implements INotebo } this.word = word.word; + // Record the total number of matches at the beginning of the selection process for performance + const notebookTextModel = this.notebookEditor.textModel; + if (notebookTextModel) { + const allMatches = notebookTextModel.findMatches(this.word, false, true, USUAL_WORD_SEPARATORS); + this.totalMatchesCount = allMatches.reduce((sum, cellMatch) => sum + cellMatch.matches.length, 0); + } + const index = this.notebookEditor.getCellIndex(focusedCell); if (index === undefined) { return; @@ -545,6 +555,14 @@ export class NotebookMultiCursorController extends Disposable implements INotebo return; // should not happen } + // Check if all matches are already covered by selections to avoid infinite looping + const totalSelections = this.trackedCells.reduce((sum, trackedCell) => sum + trackedCell.matchSelections.length, 0); + + if (totalSelections >= this.totalMatchesCount) { + // All matches are already selected, make this a no-op like in regular editors + return; + } + const findResult = notebookTextModel.findNextMatch( this.word, { cellIndex: index, position: focusedCell.getSelections()[focusedCell.getSelections().length - 1].getEndPosition() }, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index 7e40ee920d0..9a94c4a3f2b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -21,7 +21,7 @@ import { InlineChatController } from '../../../../inlineChat/browser/inlineChatC import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from '../../controller/chat/notebookChatContext.js'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from '../../controller/coreActions.js'; import { CellEditState } from '../../notebookBrowser.js'; -import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from '../../../common/notebookCommon.js'; +import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY } from '../../../common/notebookCommon.js'; import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED, IS_COMPOSITE_NOTEBOOK } from '../../../common/notebookContextKeys.js'; const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; @@ -77,6 +77,10 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction { EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ContextKeyExpr.or( + NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY.isEqualTo('end'), + NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY.isEqualTo('both') + ) ), EditorContextKeys.isEmbeddedDiffEditor.negate() ), @@ -158,6 +162,10 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction { EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ContextKeyExpr.or( + NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY.isEqualTo('start'), + NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY.isEqualTo('both') + ) ), EditorContextKeys.isEmbeddedDiffEditor.negate() ), diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts index a8dc1d801d8..4c95a37355f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts @@ -9,7 +9,8 @@ import { IListAccessibilityProvider } from '../../../../../../base/browser/ui/li import { ITreeNode, ITreeRenderer } from '../../../../../../base/browser/ui/tree/tree.js'; import { FuzzyScore } from '../../../../../../base/common/filters.js'; import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; -import { localize } from '../../../../../../nls.js'; +import { observableValue } from '../../../../../../base/common/observable.js'; +import { ILocalizedString, localize, localize2 } from '../../../../../../nls.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { WorkbenchObjectTree } from '../../../../../../platform/list/browser/listService.js'; import { DebugExpressionRenderer } from '../../../../debug/browser/debugExpressionRenderer.js'; @@ -18,6 +19,9 @@ import { INotebookVariableElement } from './notebookVariablesDataSource.js'; const $ = dom.$; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; +export const NOTEBOOK_TITLE: ILocalizedString = localize2('notebook.notebookVariables', "Notebook Variables"); +export const REPL_TITLE: ILocalizedString = localize2('notebook.ReplVariables', "REPL Variables"); + export class NotebookVariablesTree extends WorkbenchObjectTree { } export class NotebookVariablesDelegate implements IListVirtualDelegate { @@ -31,6 +35,7 @@ export class NotebookVariablesDelegate implements IListVirtualDelegate { - getWidgetAriaLabel(): string { - return localize('debugConsole', "Notebook Variables"); + private _widgetAriaLabel = observableValue('widgetAriaLabel', NOTEBOOK_TITLE.value); + + getWidgetAriaLabel() { + return this._widgetAriaLabel; + } + + updateWidgetAriaLabel(label: string): void { + this._widgetAriaLabel.set(label, undefined); } getAriaLabel(element: INotebookVariableElement): string { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index dc86e5ba940..4a836cc19b0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -6,8 +6,6 @@ import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js'; import { RunOnceScheduler } from '../../../../../../base/common/async.js'; import { URI } from '../../../../../../base/common/uri.js'; -import * as nls from '../../../../../../nls.js'; -import { ILocalizedString } from '../../../../../../platform/action/common/action.js'; import { getFlatContextMenuActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; @@ -25,7 +23,7 @@ import { IViewPaneOptions, ViewPane } from '../../../../../browser/parts/views/v import { IViewDescriptorService } from '../../../../../common/views.js'; import { CONTEXT_VARIABLE_EXTENSIONID, CONTEXT_VARIABLE_INTERFACES, CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from '../../../../debug/common/debug.js'; import { IEmptyScope, INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from './notebookVariablesDataSource.js'; -import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from './notebookVariablesTree.js'; +import { NOTEBOOK_TITLE, NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate, REPL_TITLE } from './notebookVariablesTree.js'; import { getNotebookEditorFromEditorPane } from '../../notebookBrowser.js'; import { NotebookTextModel } from '../../../common/model/notebookTextModel.js'; import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookExecutionStateService } from '../../../common/notebookExecutionStateService.js'; @@ -39,12 +37,11 @@ export type contextMenuArg = { source: string; name: string; type?: string; valu export class NotebookVariablesView extends ViewPane { static readonly ID = 'notebookVariablesView'; - static readonly NOTEBOOK_TITLE: ILocalizedString = nls.localize2('notebook.notebookVariables', "Notebook Variables"); - static readonly REPL_TITLE: ILocalizedString = nls.localize2('notebook.ReplVariables', "REPL Variables"); private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; private readonly dataSource: NotebookVariableDataSource; + private readonly accessibilityProvider: NotebookVariableAccessibilityProvider; private updateScheduler: RunOnceScheduler; @@ -73,6 +70,7 @@ export class NotebookVariablesView extends ViewPane { this._register(this.notebookExecutionStateService.onDidChangeExecution(this.handleExecutionStateChange.bind(this))); this._register(this.editorService.onDidCloseEditor((e) => this.handleCloseEditor(e))); + this.accessibilityProvider = new NotebookVariableAccessibilityProvider(); this.handleActiveEditorChange(false); this.dataSource = new NotebookVariableDataSource(this.notebookKernelService); @@ -91,7 +89,7 @@ export class NotebookVariablesView extends ViewPane { [this.instantiationService.createInstance(NotebookVariableRenderer)], this.dataSource, { - accessibilityProvider: new NotebookVariableAccessibilityProvider(), + accessibilityProvider: this.accessibilityProvider, identityProvider: { getId: (e: INotebookVariableElement) => e.id }, }); @@ -149,9 +147,11 @@ export class NotebookVariablesView extends ViewPane { this.activeNotebook = notebookDocument; if (isCompositeNotebookEditorInput(editor.input)) { - this.updateTitle(NotebookVariablesView.REPL_TITLE.value); + this.updateTitle(REPL_TITLE.value); + this.accessibilityProvider.updateWidgetAriaLabel(REPL_TITLE.value); } else { - this.updateTitle(NotebookVariablesView.NOTEBOOK_TITLE.value); + this.updateTitle(NOTEBOOK_TITLE.value); + this.accessibilityProvider.updateWidgetAriaLabel(NOTEBOOK_TITLE.value); } if (doUpdate) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 61286b39689..786603a18fb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -740,7 +740,7 @@ export class NotebookCellOutline implements IOutline { const notebookEditorOptions: INotebookEditorOptions = { ...options, override: this._editor.input?.editorId, - cellRevealType: CellRevealType.NearTopIfOutsideViewport, + cellRevealType: CellRevealType.Top, selection: entry.position, viewState: undefined, }; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index b1f977c9b33..47400cced68 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -6,6 +6,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { codiconsLibrary } from '../../../../../../base/common/codiconsLibrary.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../../base/common/network.js'; import { Position } from '../../../../../../editor/common/core/position.js'; import { Range } from '../../../../../../editor/common/core/range.js'; import { IWordAtPosition } from '../../../../../../editor/common/core/wordHelper.js'; @@ -16,11 +17,10 @@ import { localize } from '../../../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../../platform/quickinput/common/quickInput.js'; +import { IQuickInputService, IQuickPickItem } from '../../../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../../common/contributions.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; import { IChatWidget, IChatWidgetService, showChatView } from '../../../../chat/browser/chat.js'; -import { ChatInputPart } from '../../../../chat/browser/chatInputPart.js'; import { ChatDynamicVariableModel } from '../../../../chat/browser/contrib/chatDynamicVariables.js'; import { computeCompletionRanges } from '../../../../chat/browser/contrib/chatInputCompletions.js'; import { IChatAgentService } from '../../../../chat/common/chatAgents.js'; @@ -37,7 +37,7 @@ import './cellChatActions.js'; import { CTX_NOTEBOOK_CHAT_HAS_AGENT } from './notebookChatContext.js'; import { IViewsService } from '../../../../../services/views/common/viewsService.js'; import { createNotebookOutputVariableEntry, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT_CONST } from '../../contrib/chat/notebookChatUtils.js'; -import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService } from '../../../../chat/browser/chatContextPickService.js'; +import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextPicker } from '../../../../chat/browser/chatContextPickService.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; const NotebookKernelVariableKey = 'kernelVariable'; @@ -70,7 +70,7 @@ class NotebookChatContribution extends Disposable implements IWorkbenchContribut updateNotebookAgentStatus(); this._register(chatAgentService.onDidChangeAgents(updateNotebookAgentStatus)); - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatKernelDynamicCompletions', triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { @@ -260,7 +260,7 @@ class KernelVariableContextPicker implements IChatContextPickerItem { return widget.location === ChatAgentLocation.Notebook && Boolean(getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument); } - asPicker(): { readonly placeholder: string; readonly picks: Promise<(IChatContextPickerPickItem | IQuickPickSeparator)[]> } { + asPicker(): IChatContextPicker { const picks = (async () => { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 92fc827c408..f52fbf3c8d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -15,7 +15,6 @@ import { MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/a import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; -import { EditorsOrder } from '../../../../common/editor.js'; import { IDebugService } from '../../../debug/common/debug.js'; import { CTX_INLINE_CHAT_FOCUSED } from '../../../inlineChat/common/inlineChat.js'; import { insertCell } from './cellOperations.js'; @@ -71,7 +70,7 @@ function renderAllMarkdownCells(context: INotebookActionContext): void { } } -async function runCell(editorGroupsService: IEditorGroupsService, context: INotebookActionContext): Promise { +async function runCell(editorGroupsService: IEditorGroupsService, context: INotebookActionContext, editorService?: IEditorService): Promise { const group = editorGroupsService.activeGroup; if (group) { @@ -80,6 +79,11 @@ async function runCell(editorGroupsService: IEditorGroupsService, context: INote } } + // If auto-reveal is enabled, ensure the notebook editor is visible before revealing cells + if (context.autoReveal && (context.cell || context.selectedCells?.length) && editorService) { + editorService.openEditor({ resource: context.notebookEditor.textModel.uri, options: { revealIfOpened: true } }); + } + if (context.ui && context.cell) { if (context.autoReveal) { handleAutoReveal(context.cell, context.notebookEditor); @@ -238,8 +242,11 @@ registerAction2(class ExecuteNotebookAction extends NotebookAction { renderAllMarkdownCells(context); const editorService = accessor.get(IEditorService); - const editor = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).find( - editor => editor.editor instanceof NotebookEditorInput && editor.editor.viewType === context.notebookEditor.textModel.viewType && editor.editor.resource.toString() === context.notebookEditor.textModel.uri.toString()); + const editor = editorService.findEditors({ + resource: context.notebookEditor.textModel.uri, + typeId: NotebookEditorInput.ID, + editorId: context.notebookEditor.textModel.viewType + }).at(0); const editorGroupService = accessor.get(IEditorGroupsService); if (editor) { @@ -284,6 +291,7 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise { const editorGroupsService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); if (context.ui) { await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true }); @@ -304,7 +312,7 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { return; } - await runCell(editorGroupsService, context); + await runCell(editorGroupsService, context, editorService); } }); @@ -422,6 +430,7 @@ registerAction2(class ExecuteCellFocusContainer extends NotebookMultiCellAction async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise { const editorGroupsService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); if (context.ui) { await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true }); @@ -433,7 +442,7 @@ registerAction2(class ExecuteCellFocusContainer extends NotebookMultiCellAction } } - await runCell(editorGroupsService, context); + await runCell(editorGroupsService, context, editorService); } }); @@ -526,6 +535,7 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { const editorGroupsService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); const idx = context.notebookEditor.getCellIndex(context.cell); if (typeof idx !== 'number') { return; @@ -568,7 +578,7 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { } } - return runCell(editorGroupsService, context); + return runCell(editorGroupsService, context, editorService); } } }); @@ -589,6 +599,7 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { const editorGroupsService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); const idx = context.notebookEditor.getCellIndex(context.cell); const languageService = accessor.get(ILanguageService); const newFocusMode = context.cell.focusMode === CellFocusMode.Editor ? 'editor' : 'container'; @@ -601,7 +612,7 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction { if (context.cell.cellKind === CellKind.Markup) { context.cell.updateEditState(CellEditState.Preview, EXECUTE_CELL_INSERT_BELOW); } else { - runCell(editorGroupsService, context); + runCell(editorGroupsService, context, editorService); } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index a0a5557ddab..cfefb3c005c 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -860,7 +860,8 @@ abstract class AbstractElementRenderer extends Disposable { height: this.cell.layoutInfo.metadataHeight }, overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - readOnly: false + readOnly: false, + allowVariableLineHeights: false }, {}); this.layout({ metadataHeight: true }); this._metadataEditorDisposeStore.add(this._metadataEditor); @@ -956,7 +957,8 @@ abstract class AbstractElementRenderer extends Disposable { width: Math.min(OUTPUT_EDITOR_HEIGHT_MAGIC, this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, this.cell.type === 'unchanged' || this.cell.type === 'modified') - 32), height: this.cell.layoutInfo.rawOutputHeight }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + allowVariableLineHeights: false }, {}); this._outputEditorDisposeStore.add(this._outputEditor); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index 047e35327a7..c36959700cd 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -621,6 +621,7 @@ function buildSourceEditor(instantiationService: IInstantiationService, notebook }, automaticLayout: false, overflowWidgetsDomNode: notebookEditor.getOverflowContainerDomNode(), + allowVariableLineHeights: false, readOnly: true, }, { contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookCellStatusBar.css b/src/vs/workbench/contrib/notebook/browser/media/notebookCellStatusBar.css index 64dd58eecd2..e18a2855b7a 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookCellStatusBar.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookCellStatusBar.css @@ -9,6 +9,7 @@ display: flex; position: relative; overflow: hidden; + cursor: default; } .monaco-workbench .notebookOverlay .cell-statusbar-hidden .cell-statusbar-container { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts index 56e6d347e4e..96ea911c866 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts @@ -24,7 +24,6 @@ export class NotebookAccessibleView implements IAccessibleViewImplementation { } } - export function getAccessibleOutputProvider(editorService: IEditorService) { const activePane = editorService.activeEditorPane; const notebookEditor = getNotebookEditorFromEditorPane(activePane); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 531e14d8c8f..4c79dad2047 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,7 +22,7 @@ import { IEditorPane, IEditorPaneWithSelection } from '../../../common/editor.js import { CellViewModelStateChangeEvent, NotebookCellStateChangedEvent, NotebookLayoutInfo } from './notebookViewEvents.js'; import { NotebookCellTextModel } from '../common/model/notebookCellTextModel.js'; import { NotebookTextModel } from '../common/model/notebookTextModel.js'; -import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookFindOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID } from '../common/notebookCommon.js'; +import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookFindOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID, NOTEBOOK_DIFF_EDITOR_ID } from '../common/notebookCommon.js'; import { isCompositeNotebookEditorInput } from '../common/notebookEditorInput.js'; import { INotebookKernel } from '../common/notebookKernelService.js'; import { NotebookOptions } from './notebookOptions.js'; @@ -32,7 +32,6 @@ import { IEditorCommentsOptions, IEditorOptions } from '../../../../editor/commo import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IObservable } from '../../../../base/common/observable.js'; -import { NotebookTextDiffEditor } from './diff/notebookDiffEditor.js'; import { INotebookTextDiffEditor } from './diff/notebookDiffEditorBrowser.js'; //#region Shared commands @@ -931,7 +930,7 @@ export function getNotebookEditorFromEditorPane(editorPane?: IEditorPane): INote return editorPane.getControl() as INotebookEditor | undefined; } - if (editorPane.getId() === NotebookTextDiffEditor.ID) { + if (editorPane.getId() === NOTEBOOK_DIFF_EDITOR_ID) { return (editorPane.getControl() as INotebookTextDiffEditor).inlineNotebookEditor; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 3a946890acf..e7b1965ac3e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -496,7 +496,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } getSelections() { - return this.viewModel?.getSelections() ?? []; + return this.viewModel?.getSelections() ?? [{ start: 0, end: 0 }]; } setSelections(selections: ICellRange[]) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index 1797591072f..004ed1c4747 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -331,6 +331,7 @@ export class MarkupCell extends Disposable { width: width, height: editorHeight }, + allowVariableLineHeights: false, // overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts index 973f914aecb..f47594f0796 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts @@ -61,6 +61,7 @@ export class NotebookCellEditorPool extends Disposable { handleMouseWheel: false, useShadows: false, }, + allowVariableLineHeights: false, }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 3904dd608ff..867645b650a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -286,6 +286,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const editor = editorInstaService.createInstance(CodeEditorWidget, editorContainer, { ...this.editorOptions.getDefaultValue(), + allowVariableLineHeights: false, dimension: { width: 0, height: 0 diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts index d66657a4701..2f56ee0758e 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts @@ -5,21 +5,7 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { ICellRange } from '../../common/notebookRange.js'; - -function rangesEqual(a: ICellRange[], b: ICellRange[]) { - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i++) { - if (a[i].start !== b[i].start || a[i].end !== b[i].end) { - return false; - } - } - - return true; -} +import { cellRangesEqual, ICellRange } from '../../common/notebookRange.js'; // Challenge is List View talks about `element`, which needs extra work to convert to ICellRange as we support Folding and Cell Move export class NotebookCellSelectionCollection extends Disposable { @@ -27,23 +13,26 @@ export class NotebookCellSelectionCollection extends Disposable { private readonly _onDidChangeSelection = this._register(new Emitter()); get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } - private _primary: ICellRange | null = null; + private _primary: ICellRange = { start: 0, end: 0 }; - private _selections: ICellRange[] = []; + private _selections: ICellRange[] = [{ start: 0, end: 0 }]; get selections(): ICellRange[] { return this._selections; } get focus(): ICellRange { - return this._primary ?? { start: 0, end: 0 }; + return this._primary; } setState(primary: ICellRange | null, selections: ICellRange[], forceEventEmit: boolean, source: 'view' | 'model') { - const changed = primary !== this._primary || !rangesEqual(this._selections, selections); + const validPrimary = primary ?? { start: 0, end: 0 }; + const validSelections = selections.length > 0 ? selections : [{ start: 0, end: 0 }]; - this._primary = primary; - this._selections = selections; + const changed = !cellRangesEqual([validPrimary], [this._primary]) || !cellRangesEqual(this._selections, validSelections); + + this._primary = validPrimary; + this._selections = validSelections; if (changed || forceEventEmit) { this._onDidChangeSelection.fire(source); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index ea8227a1a69..813faae9352 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -6,7 +6,7 @@ import * as DOM from '../../../../../base/browser/dom.js'; import { EventType as TouchEventType } from '../../../../../base/browser/touch.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; -import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; +import { IMouseWheelEvent, StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore, type IReference } from '../../../../../base/common/lifecycle.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; @@ -164,6 +164,11 @@ export class NotebookStickyScroll extends Disposable { this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.CONTEXT_MENU, async (event: MouseEvent) => { this.onContextMenu(event); })); + + // Forward wheel events to the notebook editor to enable scrolling when hovering over sticky scroll + this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.WHEEL, (event: WheelEvent) => { + this.notebookCellList.triggerScrollFromMouseWheelEvent(event as any as IMouseWheelEvent); + })); } private onContextMenu(e: MouseEvent) { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 237e0ff8bdf..925dcdd8bc2 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -49,8 +49,8 @@ class StackOperation implements IWorkspaceUndoRedoElement { constructor( readonly textModel: NotebookTextModel, readonly undoRedoGroup: UndoRedoGroup | undefined, - private _pauseableEmitter: PauseableEmitter, - private _postUndoRedo: (alternativeVersionId: string) => void, + private readonly _pauseableEmitter: PauseableEmitter, + private readonly _postUndoRedo: (alternativeVersionId: string) => void, selectionState: ISelectionState | undefined, beginAlternativeVersionId: string ) { @@ -621,32 +621,35 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean): boolean { this._pauseableEmitter.pause(); - this._operationManager.pushStackElement(this._alternativeVersionId, undefined); - - if (computeUndoRedo && this.isOnlyEditingMetadataOnNewCells(rawEdits)) { - if (!this._operationManager.appendPreviousOperation()) { - // we can't append the previous operation, so just don't compute undo/redo - computeUndoRedo = false; - } - } else if (computeUndoRedo) { - this.newCellsFromLastEdit.clear(); - } - try { - this._doApplyEdits(rawEdits, synchronous, computeUndoRedo, beginSelectionState, undoRedoGroup); - return true; - } finally { - if (!this._pauseableEmitter.isEmpty) { - // Update selection and versionId after applying edits. - const endSelections = endSelectionsComputer(); - this._increaseVersionId(this._operationManager.isUndoStackEmpty() && !this._pauseableEmitter.isDirtyEvent()); + this._operationManager.pushStackElement(this._alternativeVersionId, undefined); - // Finalize undo element - this._operationManager.pushStackElement(this._alternativeVersionId, endSelections); - - // Broadcast changes - this._pauseableEmitter.fire({ rawEvents: [], versionId: this.versionId, synchronous: synchronous, endSelectionState: endSelections }); + if (computeUndoRedo && this.isOnlyEditingMetadataOnNewCells(rawEdits)) { + if (!this._operationManager.appendPreviousOperation()) { + // we can't append the previous operation, so just don't compute undo/redo + computeUndoRedo = false; + } + } else if (computeUndoRedo) { + this.newCellsFromLastEdit.clear(); } + + try { + this._doApplyEdits(rawEdits, synchronous, computeUndoRedo, beginSelectionState, undoRedoGroup); + return true; + } finally { + if (!this._pauseableEmitter.isEmpty) { + // Update selection and versionId after applying edits. + const endSelections = endSelectionsComputer(); + this._increaseVersionId(this._operationManager.isUndoStackEmpty() && !this._pauseableEmitter.isDirtyEvent()); + + // Finalize undo element + this._operationManager.pushStackElement(this._alternativeVersionId, endSelections); + + // Broadcast changes + this._pauseableEmitter.fire({ rawEvents: [], versionId: this.versionId, synchronous: synchronous, endSelectionState: endSelections }); + } + } + } finally { this._pauseableEmitter.resume(); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index 4f712df54e1..d11424320a4 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -17,7 +17,7 @@ import { ResourceMap } from '../../../../base/common/map.js'; import { FileWorkingCopyManager, IFileWorkingCopyManager } from '../../../services/workingCopy/common/fileWorkingCopyManager.js'; import { Schemas } from '../../../../base/common/network.js'; import { NotebookProviderInfo } from './notebookProvider.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IFileReadLimits } from '../../../../platform/files/common/files.js'; @@ -188,7 +188,7 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes } private createUntitledUri(notebookType: string) { - const info = this._notebookService.getContributedNotebookType(assertIsDefined(notebookType)); + const info = this._notebookService.getContributedNotebookType(assertReturnsDefined(notebookType)); if (!info) { throw new Error('UNKNOWN notebook type: ' + notebookType); } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index 05072b46b2e..ee55b5c5988 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -15,7 +15,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { DisposableStore } from '../../../../../base/common/lifecycle.js'; suite('NotebookSelection', () => { - ensureNoDisposablesAreLeakedInTestSuite(); + const store = ensureNoDisposablesAreLeakedInTestSuite(); test('focus is never empty', function () { const selectionCollection = new NotebookCellSelectionCollection(); @@ -25,6 +25,64 @@ suite('NotebookSelection', () => { assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 }); selectionCollection.dispose(); }); + + test('selection is never empty', function () { + const selectionCollection = new NotebookCellSelectionCollection(); + assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 0 }]); + + selectionCollection.setState(null, [], true, 'model'); + assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 0 }]); + selectionCollection.dispose(); + }); + + test('selections does not change when setting to empty', function () { + const selectionCollection = new NotebookCellSelectionCollection(); + let changed = false; + store.add(selectionCollection.onDidChangeSelection(() => { + changed = true; + })); + + selectionCollection.setState(null, [], false, 'model'); + assert.strictEqual(changed, false); + selectionCollection.setState({ start: 0, end: 0 }, [], false, 'model'); + assert.strictEqual(changed, false); + selectionCollection.setState({ start: 0, end: 0 }, [{ start: 0, end: 0 }], false, 'model'); + assert.strictEqual(changed, false); + selectionCollection.setState(null, [], false, 'model'); + assert.strictEqual(changed, false); + selectionCollection.dispose(); + }); + + test('event fires when selection or focus changes', function () { + const selectionCollection = new NotebookCellSelectionCollection(); + let eventCount = 0; + store.add(selectionCollection.onDidChangeSelection(() => { + eventCount++; + })); + + // Change focus + selectionCollection.setState({ start: 1, end: 1 }, [{ start: 1, end: 2 }], false, 'model'); + assert.strictEqual(eventCount, 1); + + // Change selections + selectionCollection.setState({ start: 1, end: 1 }, [{ start: 1, end: 2 }, { start: 2, end: 3 }], false, 'model'); + assert.strictEqual(eventCount, 2); + + // no change + selectionCollection.setState({ start: 1, end: 1 }, [{ start: 1, end: 2 }, { start: 2, end: 3 }], false, 'model'); + assert.strictEqual(eventCount, 2); + + // change to empty focus + selectionCollection.setState({ start: 0, end: 0 }, [{ start: 4, end: 5 }], false, 'model'); + assert.strictEqual(eventCount, 3); + + // change to empty selections + selectionCollection.setState({ start: 0, end: 0 }, [], false, 'model'); + assert.strictEqual(eventCount, 4); + + selectionCollection.dispose(); + }); + }); suite('NotebookCellList focus/selection', () => { @@ -312,6 +370,9 @@ suite('NotebookCellList focus/selection', () => { // viewModel.deleteCell(1, true, false); assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); + runDeleteAction(editor, viewModel.cellAt(0)!); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 0 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 0 }]); }); }); diff --git a/src/vs/workbench/contrib/performance/electron-sandbox/performance.contribution.ts b/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/performance/electron-sandbox/performance.contribution.ts rename to src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts diff --git a/src/vs/workbench/contrib/performance/electron-sandbox/rendererAutoProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/rendererAutoProfiler.ts similarity index 97% rename from src/vs/workbench/contrib/performance/electron-sandbox/rendererAutoProfiler.ts rename to src/vs/workbench/contrib/performance/electron-browser/rendererAutoProfiler.ts index 5286ecc047b..52edafd6cff 100644 --- a/src/vs/workbench/contrib/performance/electron-sandbox/rendererAutoProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/rendererAutoProfiler.ts @@ -12,8 +12,8 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { IV8Profile } from '../../../../platform/profiling/common/profiling.js'; -import { IProfileAnalysisWorkerService, ProfilingOutput } from '../../../../platform/profiling/electron-sandbox/profileAnalysisWorkerService.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { IProfileAnalysisWorkerService, ProfilingOutput } from '../../../../platform/profiling/electron-browser/profileAnalysisWorkerService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { parseExtensionDevOptions } from '../../../services/extensions/common/extensionDevOptions.js'; import { ITimerService } from '../../../services/timer/browser/timerService.js'; diff --git a/src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts similarity index 99% rename from src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts rename to src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index 448ebcba59f..f5b6f208aa1 100644 --- a/src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -8,7 +8,7 @@ import { localize } from '../../../../nls.js'; import { dirname, basename } from '../../../../base/common/resources.js'; import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { PerfviewContrib } from '../browser/perfviewEditor.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; diff --git a/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts similarity index 99% rename from src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts rename to src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index 7753789e588..fe0d86ad970 100644 --- a/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -6,7 +6,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { timeout } from '../../../../base/common/async.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index af01934653d..6a34c4f6ec5 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -22,7 +22,7 @@ import { ThemeColor } from '../../../../base/common/themables.js'; import { overviewRulerInfo, overviewRulerError } from '../../../../editor/common/core/editorColorRegistry.js'; import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness, OverviewRulerLane } from '../../../../editor/common/model.js'; import { KeybindingParser } from '../../../../base/common/keybindingParser.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { isEqual } from '../../../../base/common/resources.js'; import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; import { DEFINE_KEYBINDING_EDITOR_CONTRIB_ID, IDefineKeybindingEditorContribution } from '../../../services/preferences/common/preferences.js'; @@ -100,7 +100,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { this._updateDecorations = this._register(new RunOnceScheduler(() => this._updateDecorationsNow(), 500)); - const model = assertIsDefined(this._editor.getModel()); + const model = assertReturnsDefined(this._editor.getModel()); this._register(model.onDidChangeContent(() => this._updateDecorations.schedule())); this._register(this._keybindingService.onDidUpdateKeybindings(() => this._updateDecorations.schedule())); this._register({ @@ -113,7 +113,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { } private _updateDecorationsNow(): void { - const model = assertIsDefined(this._editor.getModel()); + const model = assertReturnsDefined(this._editor.getModel()); const newDecorations: IModelDeltaDecoration[] = []; diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index fe8258a1ce4..5aae7a8df36 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -40,13 +40,13 @@ .settings-editor > .settings-header > .search-container > .settings-count-widget { position: absolute; - right: 46px; + right: 48px; top: 0px; - margin: 4px 0px; + margin: 2.5px 0px; } .settings-editor > .settings-header > .search-container.with-ai-toggle > .settings-count-widget { - right: 65px; + right: 69px; } .settings-editor > .settings-header > .search-container > .settings-count-widget:empty { @@ -60,19 +60,19 @@ top: 0; right: 0; height: 100%; - width: 43px; -} - -.settings-editor > .settings-header > .search-container.with-ai-toggle > .settings-clear-widget { - width: 62px; } .settings-editor > .settings-header > .search-container > .settings-clear-widget .action-label { - padding: 2px; + padding: 3px; margin-left: 0px; box-sizing: content-box; } +.settings-editor > .settings-header > .search-container > .settings-clear-widget .action-label.monaco-custom-toggle { + /* To offset the border width. */ + padding: 2.3px; +} + .settings-editor > .settings-header > .settings-header-controls { display: flex; flex-wrap: wrap; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 29b4bbb3b50..2333c5f65e1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -59,7 +59,7 @@ import { nullRange, Settings2EditorModel } from '../../../services/preferences/c import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; import { IUserDataSyncWorkbenchService } from '../../../services/userDataSync/common/userDataSync.js'; import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_FETCH_TIMEOUT_MS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, getExperimentalExtensionToggleData, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from '../common/preferences.js'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_FETCH_TIMEOUT_MS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, getExperimentalExtensionToggleData, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WorkbenchSettingsEditorSettings, WORKSPACE_TRUST_SETTING_TAG } from '../common/preferences.js'; import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from '../common/settingsEditorColorRegistry.js'; import './media/settingsEditor2.css'; import { preferencesAiResultsIcon, preferencesClearInputIcon, preferencesFilterIcon } from './preferencesIcons.js'; @@ -164,6 +164,7 @@ export class SettingsEditor2 extends EditorPane { private rootElement!: HTMLElement; private headerContainer!: HTMLElement; + private searchContainer: HTMLElement | null = null; private bodyContainer!: HTMLElement; private searchWidget!: SuggestEnabledInput; private countElement!: HTMLElement; @@ -230,6 +231,8 @@ export class SettingsEditor2 extends EditorPane { private readonly inputChangeListener: MutableDisposable; + private searchInputActionBar: ActionBar | null = null; + constructor( group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @@ -280,6 +283,10 @@ export class SettingsEditor2 extends EditorPane { .split(this.DISMISSED_EXTENSION_SETTINGS_DELIMITER); this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectedKeys.has(WorkbenchSettingsEditorSettings.ShowAISearchToggle)) { + const isToggleVisible = this.configurationService.getValue(WorkbenchSettingsEditorSettings.ShowAISearchToggle); + this.updateAISearchToggleVisibility(isToggleVisible); + } if (e.source !== ConfigurationTarget.DEFAULT) { this.onConfigUpdate(e.affectedKeys); } @@ -328,6 +335,24 @@ export class SettingsEditor2 extends EditorPane { }); } + private updateAISearchToggleVisibility(isVisible: boolean): void { + if (!this.searchContainer || !this.showAiResultsAction || !this.searchInputActionBar) { + return; + } + + if (isVisible) { + this.searchInputActionBar.push(this.showAiResultsAction, { + index: 1, + label: false, + icon: true + }); + this.searchContainer.classList.add('with-ai-toggle'); + } else if (this.searchInputActionBar.hasAction(this.showAiResultsAction)) { + this.searchInputActionBar.pull(1); + this.searchContainer.classList.remove('with-ai-toggle'); + } + } + override get minimumWidth(): number { return SettingsEditor2.EDITOR_MIN_WIDTH; } override get maximumWidth(): number { return Number.POSITIVE_INFINITY; } override get minimumHeight() { return 180; } @@ -621,35 +646,37 @@ export class SettingsEditor2 extends EditorPane { */ private createHeader(parent: HTMLElement): void { this.headerContainer = DOM.append(parent, $('.settings-header')); - - const searchContainer = DOM.append(this.headerContainer, $('.search-container')); + this.searchContainer = DOM.append(this.headerContainer, $('.search-container')); const clearInputAction = this._register(new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), ThemeIcon.asClassName(preferencesClearInputIcon), false, async () => this.clearSearchResults() )); - const setupHidden = this.contextKeyService.getContextKeyValue('chatSetupHidden'); - const showSuggestions = this.configurationService.getValue('workbench.settings.showAISearchToggle'); - if (!setupHidden && showSuggestions) { - const showAiResultActionClassNames = ['action-label', ThemeIcon.asClassName(preferencesAiResultsIcon)]; - const searchServiceEnabled = this.aiSettingsSearchService.isEnabled(); - this.showAiResultsAction = this._register(new Action(SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS, - localize('showAiResultsDescription', "Search settings with AI"), showAiResultActionClassNames.join(' '), searchServiceEnabled - )); - this._register(this.aiSettingsSearchService.onProviderRegistered(() => { - this.showAiResultsAction!.enabled = true; - })); - this._register(this.showAiResultsAction.onDidChange(() => { - this.onSearchInputChanged(true); - })); - } + const showAiResultActionClassNames = ['action-label', ThemeIcon.asClassName(preferencesAiResultsIcon)]; + const searchServiceEnabled = this.aiSettingsSearchService.isEnabled(); + + this.showAiResultsAction = this._register(new Action(SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS, + searchServiceEnabled + ? localize('showAiResultsDescription', "Search settings with AI") + : localize('showAiResultsNotReady', "AI search functionality loading..."), + showAiResultActionClassNames.join(' '), searchServiceEnabled + )); + this._register(this.aiSettingsSearchService.onProviderRegistered(() => { + if (this.showAiResultsAction) { + this.showAiResultsAction.label = localize('showAiResultsDescription', "Search settings with AI"); + this.showAiResultsAction.enabled = true; + } + })); + this._register(this.showAiResultsAction.onDidChange(() => { + this.onSearchInputChanged(true); + })); const filterAction = this._register(new Action(SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, localize('filterInput', "Filter Settings"), ThemeIcon.asClassName(preferencesFilterIcon) )); - this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, { + this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, this.searchContainer, { triggerCharacters: ['@', ':'], provideResults: (query: string) => { // Based on testing, the trigger character is always at the end of the query. @@ -682,7 +709,7 @@ export class SettingsEditor2 extends EditorPane { this._currentFocusContext = SettingsFocusContext.Search; })); - this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget.monaco-count-badge.long')); + this.countElement = DOM.append(this.searchContainer, DOM.$('.settings-count-widget.monaco-count-badge.long')); this.countElement.style.backgroundColor = asCssVariable(badgeBackground); this.countElement.style.color = asCssVariable(badgeForeground); @@ -716,28 +743,26 @@ export class SettingsEditor2 extends EditorPane { })); } - this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); + this.controlsElement = DOM.append(this.searchContainer, DOM.$('.settings-clear-widget')); - const actionBar = this._register(new ActionBar(this.controlsElement, { + this.searchInputActionBar = this._register(new ActionBar(this.controlsElement, { actionViewItemProvider: (action, options) => { if (action.id === filterAction.id) { return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, options, this.actionRunner, this.searchWidget); } if (this.showAiResultsAction && action.id === this.showAiResultsAction.id) { - return new ToggleActionViewItem(null, action, { ...options, toggleStyles: defaultToggleStyles }); + return new ToggleActionViewItem(null, action, { ...options, keybinding: 'Ctrl+I', toggleStyles: defaultToggleStyles }); } return undefined; } })); - if (!this.showAiResultsAction) { - const actionsToPush = [clearInputAction, filterAction]; - actionBar.push(actionsToPush, { label: false, icon: true }); - } else { - const actionsToPush = [clearInputAction, this.showAiResultsAction, filterAction]; - searchContainer.classList.add('with-ai-toggle'); - actionBar.push(actionsToPush, { label: false, icon: true }); - } + const actionsToPush = [clearInputAction, filterAction]; + this.searchInputActionBar.push(actionsToPush, { label: false, icon: true }); + + const setupHidden = this.contextKeyService.getContextKeyValue('chatSetupHidden'); + const showSuggestions = this.configurationService.getValue(WorkbenchSettingsEditorSettings.ShowAISearchToggle); + this.updateAISearchToggleVisibility(!setupHidden && showSuggestions); } toggleAiSearch(): void { diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index beb5085ade7..13e3f3ee5f8 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -110,6 +110,10 @@ export const ENABLE_LANGUAGE_FILTER = true; export const ENABLE_EXTENSION_TOGGLE_SETTINGS = true; export const EXTENSION_FETCH_TIMEOUT_MS = 1000; +export enum WorkbenchSettingsEditorSettings { + ShowAISearchToggle = 'workbench.settings.showAISearchToggle', +} + export type ExtensionToggleData = { settingsEditorRecommendedExtensions: IStringDictionary; recommendedExtensionsGalleryInfo: IStringDictionary; diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorer.contribution.ts b/src/vs/workbench/contrib/processExplorer/electron-browser/processExplorer.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorer.contribution.ts rename to src/vs/workbench/contrib/processExplorer/electron-browser/processExplorer.contribution.ts diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerControl.ts b/src/vs/workbench/contrib/processExplorer/electron-browser/processExplorerControl.ts similarity index 100% rename from src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerControl.ts rename to src/vs/workbench/contrib/processExplorer/electron-browser/processExplorerControl.ts diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditor.ts b/src/vs/workbench/contrib/processExplorer/electron-browser/processExplorerEditor.ts similarity index 100% rename from src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditor.ts rename to src/vs/workbench/contrib/processExplorer/electron-browser/processExplorerEditor.ts diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 674ff723e53..c136e0fadc0 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -30,7 +30,7 @@ interface IConfiguration extends IWindowsConfiguration { editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' }; security?: { workspace?: { trust?: { enabled?: boolean } }; restrictUNCAccess?: boolean }; window: IWindowSettings; - workbench?: { enableExperiments?: boolean; settings?: { showSuggestions?: boolean } }; + workbench?: { enableExperiments?: boolean }; telemetry?: { feedback?: { enabled?: boolean } }; _extensionsGallery?: { enablePPE?: boolean }; accessibility?: { verbosity?: { debug?: boolean } }; @@ -50,7 +50,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo 'editor.accessibilitySupport', 'security.workspace.trust.enabled', 'workbench.enableExperiments', - 'workbench.settings.showAISearchToggle', '_extensionsGallery.enablePPE', 'security.restrictUNCAccess', 'accessibility.verbosity.debug', @@ -73,7 +72,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private readonly accessibilityVerbosityDebug = new ChangeObserver('boolean'); private readonly useFileStorage = new ChangeObserver('boolean'); private readonly telemetryFeedbackEnabled = new ChangeObserver('boolean'); - private readonly showSuggestions = new ChangeObserver('boolean'); constructor( @IHostService private readonly hostService: IHostService, @@ -168,9 +166,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo // Enable Feedback processChanged(this.telemetryFeedbackEnabled.handleChange(config.telemetry?.feedback?.enabled)); - // Settings editor suggestions - processChanged(this.showSuggestions.handleChange(config.workbench?.settings?.showSuggestions)); - if (askToRelaunch && changed && this.hostService.hasFocus) { this.doConfirm( isNative ? diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts similarity index 99% rename from src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts rename to src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 7443bf9cac1..95ecd96f3fd 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -16,9 +16,9 @@ import { ILabelService } from '../../../../platform/label/common/label.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { Schemas } from '../../../../base/common/network.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { ipcRenderer } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ipcRenderer } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from '../../../../platform/diagnostics/common/diagnostics.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { PersistentConnectionEventType } from '../../../../platform/remote/common/remoteAgentConnection.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts rename to src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts diff --git a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts index 61e6351ad26..ec41740e8d7 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts @@ -147,7 +147,7 @@ export class ReplDocumentContribution extends Disposable implements IWorkbenchCo // untitled notebooks are disposed when they get saved. we should not hold a reference // to such a disposed notebook and therefore dispose the reference as well - ref.object.notebook.onWillDispose(() => { + Event.once(ref.object.notebook.onWillDispose)(() => { ref.dispose(); }); const label = (options as INotebookEditorOptions)?.label ?? undefined; diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts index b6ff66566c1..667b9c48d46 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts @@ -485,11 +485,11 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { } })); - this._codeEditorWidget.onDidChangeModelDecorations(() => { + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelDecorations(() => { if (this.isVisible()) { this._updateInputHint(); } - }); + })); const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this._contextKeyService); if (input.resource && input.historyService.has(input.resource)) { diff --git a/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/src/vs/workbench/contrib/scm/browser/scmHistory.ts index 7a6e840155a..71faef70db0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistory.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -10,7 +10,6 @@ import { asCssVariable, ColorIdentifier, registerColor } from '../../../../platf import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel } from '../common/history.js'; import { rot } from '../../../../base/common/numbers.js'; import { svgElem } from '../../../../base/browser/dom.js'; -import { compareHistoryItemRefs } from './util.js'; export const SWIMLANE_HEIGHT = 22; export const SWIMLANE_WIDTH = 11; @@ -365,3 +364,31 @@ export function getHistoryItemIndex(historyItemViewModel: ISCMHistoryItemViewMod // Circle index - use the input swimlane index if present, otherwise add it to the end return inputIndex !== -1 ? inputIndex : inputSwimlanes.length; } + +export function compareHistoryItemRefs( + ref1: ISCMHistoryItemRef, + ref2: ISCMHistoryItemRef, + currentHistoryItemRef?: ISCMHistoryItemRef, + currentHistoryItemRemoteRef?: ISCMHistoryItemRef, + currentHistoryItemBaseRef?: ISCMHistoryItemRef +): number { + const getHistoryItemRefOrder = (ref: ISCMHistoryItemRef) => { + if (ref.id === currentHistoryItemRef?.id) { + return 1; + } else if (ref.id === currentHistoryItemRemoteRef?.id) { + return 2; + } else if (ref.id === currentHistoryItemBaseRef?.id) { + return 3; + } else if (ref.color !== undefined) { + return 4; + } + + return 99; + }; + + // Assign order (current > remote > base > color) + const ref1Order = getHistoryItemRefOrder(ref1); + const ref2Order = getHistoryItemRefOrder(ref2); + + return ref1Order - ref2Order; +} diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 9de801b4541..7a2c3ab47f5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -22,7 +22,7 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatWidget, showChatView } from '../../chat/browser/chat.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ISCMHistoryItemVariableEntry } from '../../chat/common/chatModel.js'; +import { ISCMHistoryItemVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { ScmHistoryItemResolver } from '../../multiDiffEditor/browser/scmMultiDiffSourceResolver.js'; import { ISCMHistoryItem } from '../common/history.js'; import { ISCMProvider, ISCMService, ISCMViewService } from '../common/scm.js'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 1b4c2a31c44..56a5397e985 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1561,6 +1561,7 @@ class SCMInputWidgetEditorOptions { return { ...getSimpleEditorOptions(this.configurationService), ...this.getEditorOptions(), + allowVariableLineHeights: false, dragAndDrop: true, dropIntoEditor: { enabled: true }, formatOnType: true, diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 2334f15157f..f838ba6ac9b 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -5,7 +5,7 @@ import { localize } from '../../../../nls.js'; import * as platform from '../../../../base/common/platform.js'; -import { ISCMHistoryItem, ISCMHistoryItemRef, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; +import { ISCMHistoryItem, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService, ISCMProvider } from '../common/scm.js'; import { IMenu, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; @@ -224,31 +224,3 @@ export function getHistoryItemHoverContent(themeService: IThemeService, historyI return { markdown, markdownNotSupportedFallback: historyItem.message }; } - -export function compareHistoryItemRefs( - ref1: ISCMHistoryItemRef, - ref2: ISCMHistoryItemRef, - currentHistoryItemRef?: ISCMHistoryItemRef, - currentHistoryItemRemoteRef?: ISCMHistoryItemRef, - currentHistoryItemBaseRef?: ISCMHistoryItemRef -): number { - const getHistoryItemRefOrder = (ref: ISCMHistoryItemRef) => { - if (ref.id === currentHistoryItemRef?.id) { - return 1; - } else if (ref.id === currentHistoryItemRemoteRef?.id) { - return 2; - } else if (ref.id === currentHistoryItemBaseRef?.id) { - return 3; - } else if (ref.color !== undefined) { - return 4; - } - - return 99; - }; - - // Assign order (current > remote > base > color) - const ref1Order = getHistoryItemRefOrder(ref1); - const ref2Order = getHistoryItemRefOrder(ref2); - - return ref1Order - ref2Order; -} diff --git a/src/vs/workbench/contrib/search/browser/chatContributions.ts b/src/vs/workbench/contrib/search/browser/chatContributions.ts index 3a039e6d339..2e8aa007859 100644 --- a/src/vs/workbench/contrib/search/browser/chatContributions.ts +++ b/src/vs/workbench/contrib/search/browser/chatContributions.ts @@ -14,7 +14,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { getExcludes, IFileQuery, ISearchComplete, ISearchConfiguration, ISearchService, QueryType, VIEW_ID } from '../../../services/search/common/search.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextValueItem, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; -import { IChatRequestVariableEntry, ISymbolVariableEntry } from '../../chat/common/chatModel.js'; +import { IChatRequestVariableEntry, ISymbolVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { SearchContext } from '../common/constants.js'; import { SearchView } from './searchView.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index 8f8bf18d96c..da1c42c240f 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -16,7 +16,7 @@ import { SearchEditor } from '../../searchEditor/browser/searchEditor.js'; import { SearchEditorInput } from '../../searchEditor/browser/searchEditorInput.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; @@ -46,7 +46,7 @@ registerAction2(class ToggleQueryDetailsAction extends Action2 { (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(args[0]?.show); } else if (contextService.getValue(Constants.SearchContext.SearchViewFocusedKey.serialize())) { const searchView = getSearchView(accessor.get(IViewsService)); - assertIsDefined(searchView).toggleQueryDetails(undefined, args[0]?.show); + assertReturnsDefined(searchView).toggleQueryDetails(undefined, args[0]?.show); } } }); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 06c660a0325..8e408accee9 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -10,7 +10,7 @@ import { Delayer } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import './media/searchEditor.css'; import { ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; @@ -137,7 +137,7 @@ export class SearchEditor extends AbstractTextCodeEditor super.createEditor(searchResultContainer); this.registerEditorListeners(); - const scopedContextKeyService = assertIsDefined(this.scopedContextKeyService); + const scopedContextKeyService = assertReturnsDefined(this.scopedContextKeyService); InSearchEditor.bindTo(scopedContextKeyService).set(true); this.createQueryEditor( diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts index f833d6f0123..15c1e4442df 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts @@ -11,7 +11,7 @@ import { IInstantiationService, ServicesAccessor } from '../../../../platform/in import { parseSavedSearchEditor, parseSerializedSearchEditor } from './searchEditorSerialization.js'; import { IWorkingCopyBackupService } from '../../../services/workingCopy/common/workingCopyBackup.js'; import { SearchConfiguration, SearchEditorWorkingCopyTypeId } from './constants.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { createTextBufferFactoryFromStream } from '../../../../editor/common/model/textModel.js'; import { Emitter } from '../../../../base/common/event.js'; import { ResourceMap } from '../../../../base/common/map.js'; @@ -33,7 +33,7 @@ export class SearchEditorModel { ) { } async resolve(): Promise { - return assertIsDefined(searchEditorModelFactory.models.get(this.resource)).resolve(); + return assertReturnsDefined(searchEditorModelFactory.models.get(this.resource)).resolve(); } } diff --git a/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 935a7432d71..5a061483a4e 100644 --- a/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -17,7 +17,7 @@ import { IWorkbenchEnvironmentService } from '../../../services/environment/comm import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import * as perf from '../../../../base/common/performance.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { ISplashStorageService } from './splash.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; @@ -92,11 +92,11 @@ export class PartsSplash { layoutInfo: !this._shouldSaveLayoutInfo() ? undefined : { sideBarSide: this._layoutService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', editorPartMinWidth: DEFAULT_EDITOR_MIN_DIMENSIONS.width, - titleBarHeight: this._layoutService.isVisible(Parts.TITLEBAR_PART, mainWindow) ? dom.getTotalHeight(assertIsDefined(this._layoutService.getContainer(mainWindow, Parts.TITLEBAR_PART))) : 0, - activityBarWidth: this._layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? dom.getTotalWidth(assertIsDefined(this._layoutService.getContainer(mainWindow, Parts.ACTIVITYBAR_PART))) : 0, - sideBarWidth: this._layoutService.isVisible(Parts.SIDEBAR_PART) ? dom.getTotalWidth(assertIsDefined(this._layoutService.getContainer(mainWindow, Parts.SIDEBAR_PART))) : 0, - auxiliarySideBarWidth: this._layoutService.isVisible(Parts.AUXILIARYBAR_PART) ? dom.getTotalWidth(assertIsDefined(this._layoutService.getContainer(mainWindow, Parts.AUXILIARYBAR_PART))) : 0, - statusBarHeight: this._layoutService.isVisible(Parts.STATUSBAR_PART, mainWindow) ? dom.getTotalHeight(assertIsDefined(this._layoutService.getContainer(mainWindow, Parts.STATUSBAR_PART))) : 0, + titleBarHeight: this._layoutService.isVisible(Parts.TITLEBAR_PART, mainWindow) ? dom.getTotalHeight(assertReturnsDefined(this._layoutService.getContainer(mainWindow, Parts.TITLEBAR_PART))) : 0, + activityBarWidth: this._layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? dom.getTotalWidth(assertReturnsDefined(this._layoutService.getContainer(mainWindow, Parts.ACTIVITYBAR_PART))) : 0, + sideBarWidth: this._layoutService.isVisible(Parts.SIDEBAR_PART) ? dom.getTotalWidth(assertReturnsDefined(this._layoutService.getContainer(mainWindow, Parts.SIDEBAR_PART))) : 0, + auxiliarySideBarWidth: this._layoutService.isVisible(Parts.AUXILIARYBAR_PART) ? dom.getTotalWidth(assertReturnsDefined(this._layoutService.getContainer(mainWindow, Parts.AUXILIARYBAR_PART))) : 0, + statusBarHeight: this._layoutService.isVisible(Parts.STATUSBAR_PART, mainWindow) ? dom.getTotalHeight(assertReturnsDefined(this._layoutService.getContainer(mainWindow, Parts.STATUSBAR_PART))) : 0, windowBorder: this._layoutService.hasMainWindowBorder(), windowBorderRadius: this._layoutService.getMainWindowBorderRadius() } diff --git a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/splash.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts rename to src/vs/workbench/contrib/splash/electron-browser/splash.contribution.ts diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/tags.contribution.ts b/src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/tags/electron-sandbox/tags.contribution.ts rename to src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts similarity index 100% rename from src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts rename to src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts similarity index 100% rename from src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts rename to src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index a9780480c6d..7c2219db3f2 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus import { IOutputChannelRegistry, Extensions as OutputExt } from '../../../services/output/common/output.js'; -import { ITaskEvent, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE, TASK_TERMINAL_ACTIVE, TaskEventKind } from '../common/tasks.js'; +import { ITaskEvent, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE, TASK_TERMINAL_ACTIVE, TaskEventKind, rerunTaskIcon, RerunForActiveTerminalCommandId } from '../common/tasks.js'; import { ITaskService, TaskCommandsRegistered, TaskExecutionSupportedContext } from '../common/taskService.js'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; @@ -40,8 +40,6 @@ import { TaskDefinitionRegistry } from '../common/taskDefinitionRegistry.js'; import { TerminalMenuBarGroup } from '../../terminal/browser/terminalMenus.js'; import { isString } from '../../../../base/common/types.js'; import { promiseWithResolvers } from '../../../../base/common/async.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { TerminalContextKeys } from '../../terminal/common/terminalContextKey.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; @@ -566,8 +564,6 @@ configurationRegistry.registerConfiguration({ } }); -export const rerunTaskIcon = registerIcon('rerun-task', Codicon.refresh, nls.localize('rerunTaskIcon', 'View icon of the rerun task.')); -export const RerunForActiveTerminalCommandId = 'workbench.action.tasks.rerunForActiveTerminal'; registerAction2(class extends Action2 { constructor() { super({ @@ -595,4 +591,3 @@ registerAction2(class extends Action2 { } } }); - diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index 4e8404b1a55..528341bae74 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -13,7 +13,7 @@ import { ITaskService, Task } from '../common/taskService.js'; import { ITerminalInstance } from '../../terminal/browser/terminal.js'; import { MarkerSeverity } from '../../../../platform/markers/common/markers.js'; import { spinningLoading } from '../../../../platform/theme/common/iconRegistry.js'; -import { IMarker } from '../../../../platform/terminal/common/capabilities/capabilities.js'; +import type { IMarker } from '@xterm/xterm'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { ITerminalStatus } from '../../terminal/common/terminal.js'; diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index a2b242d65d1..3fbca0976b9 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -40,7 +40,7 @@ import { TaskTerminalStatus } from './taskTerminalStatus.js'; import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from '../common/problemCollectors.js'; import { GroupKind } from '../common/taskConfiguration.js'; import { IResolveSet, IResolvedVariables, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from '../common/taskSystem.js'; -import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IConfigurationProperties, IExtensionTaskSource, IPresentationOptions, IShellConfiguration, IShellQuotingOptions, ITaskEvent, InMemoryTask, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, TASK_TERMINAL_ACTIVE, Task, TaskEvent, TaskEventKind, TaskScope, TaskSourceKind } from '../common/tasks.js'; +import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IConfigurationProperties, IExtensionTaskSource, IPresentationOptions, IShellConfiguration, IShellQuotingOptions, ITaskEvent, InMemoryTask, PanelKind, RerunForActiveTerminalCommandId, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, TASK_TERMINAL_ACTIVE, Task, TaskEvent, TaskEventKind, TaskScope, TaskSourceKind, rerunTaskIcon } from '../common/tasks.js'; import { ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../terminal/browser/terminal.js'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from '../../terminal/browser/terminalEscapeSequences.js'; import { TerminalProcessExtHostProxy } from '../../terminal/browser/terminalProcessExtHostProxy.js'; @@ -50,7 +50,6 @@ import { IWorkbenchEnvironmentService } from '../../../services/environment/comm import { IOutputService } from '../../../services/output/common/output.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IPathService } from '../../../services/path/common/pathService.js'; -import { RerunForActiveTerminalCommandId, rerunTaskIcon } from './task.contribution.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; interface ITerminalData { @@ -184,6 +183,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private _activeTasks: IStringDictionary; private _busyTasks: IStringDictionary; + private _taskErrors: IStringDictionary; // Tracks which tasks had errors from problem matchers + private _taskDependencies: IStringDictionary; // Tracks which tasks depend on which other tasks private _terminals: IStringDictionary; private _idleTaskTerminals: LinkedMap; private _sameTaskTerminals: IStringDictionary; @@ -243,6 +244,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._activeTasks = Object.create(null); this._busyTasks = Object.create(null); + this._taskErrors = Object.create(null); + this._taskDependencies = Object.create(null); this._terminals = Object.create(null); this._idleTaskTerminals = new LinkedMap(); this._sameTaskTerminals = Object.create(null); @@ -528,6 +531,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const dependencyTask = await resolver.resolve(dependency.uri, dependency.task); if (dependencyTask) { this._adoptConfigurationForDependencyTask(dependencyTask, task); + + // Track the dependency relationship + const taskMapKey = task.getMapKey(); + const dependencyMapKey = dependencyTask.getMapKey(); + if (!this._taskDependencies[taskMapKey]) { + this._taskDependencies[taskMapKey] = []; + } + if (!this._taskDependencies[taskMapKey].includes(dependencyMapKey)) { + this._taskDependencies[taskMapKey].push(dependencyMapKey); + } let taskResult; const commonKey = dependencyTask.getCommonTaskId(); if (nextLiveDependencies.has(commonKey)) { @@ -600,6 +613,33 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); } + private _taskHasErrors(task: Task): boolean { + const taskMapKey = task.getMapKey(); + + // Check if this task itself had errors + if (this._taskErrors[taskMapKey]) { + return true; + } + + // Check if any tracked dependencies had errors + const dependencies = this._taskDependencies[taskMapKey]; + if (dependencies) { + for (const dependencyMapKey of dependencies) { + if (this._taskErrors[dependencyMapKey]) { + return true; + } + } + } + + return false; + } + + private _cleanupTaskTracking(task: Task): void { + const taskMapKey = task.getMapKey(); + delete this._taskErrors[taskMapKey]; + delete this._taskDependencies[taskMapKey]; + } + private _adoptConfigurationForDependencyTask(dependencyTask: Task, task: Task): void { if (dependencyTask.configurationProperties.icon) { dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id; @@ -852,6 +892,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (eventCounter === 0) { if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity && (watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) { + this._taskErrors[task.getMapKey()] = true; this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherFoundErrors, task, terminal?.instanceId)); const reveal = task.command.presentation!.reveal; const revealProblems = task.command.presentation!.revealProblems; @@ -862,7 +903,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._terminalGroupService.showPanel(false); } } else { - this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherEnded, task, terminal?.instanceId)); + this._fireTaskEvent(TaskEvent.problemMatcherEnded(task, this._taskHasErrors(task), terminal?.instanceId)); } } } @@ -1000,9 +1041,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherStarted, task, terminal?.instanceId)); } else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) { if (startStopProblemMatcher.numberOfMatches && startStopProblemMatcher.maxMarkerSeverity && startStopProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error) { + this._taskErrors[task.getMapKey()] = true; this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherFoundErrors, task, terminal?.instanceId)); } else { - this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherEnded, task, terminal?.instanceId)); + this._fireTaskEvent(TaskEvent.problemMatcherEnded(task, this._taskHasErrors(task), terminal?.instanceId)); } } })); @@ -1069,11 +1111,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } this._fireTaskEvent(TaskEvent.general(TaskEventKind.Inactive, task, terminal?.instanceId)); if (startStopProblemMatcher.numberOfMatches && startStopProblemMatcher.maxMarkerSeverity && startStopProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error) { + this._taskErrors[task.getMapKey()] = true; this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherFoundErrors, task, terminal?.instanceId)); } else { - this._fireTaskEvent(TaskEvent.general(TaskEventKind.ProblemMatcherEnded, task, terminal?.instanceId)); + this._fireTaskEvent(TaskEvent.problemMatcherEnded(task, this._taskHasErrors(task), terminal?.instanceId)); } this._fireTaskEvent(TaskEvent.general(TaskEventKind.End, task, terminal?.instanceId)); + this._cleanupTaskTracking(task); resolve({ exitCode: exitCode ?? undefined }); }); }); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 5e15a9fd76f..8f40c65ffda 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -17,6 +17,8 @@ import { TaskDefinitionRegistry } from './taskDefinitionRegistry.js'; import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js'; import { TerminalExitReason } from '../../../../platform/terminal/common/terminal.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; @@ -1203,7 +1205,7 @@ export interface ITaskProblemMatcherEndedEvent extends ITaskCommon { } export interface ITaskGeneralEvent extends ITaskCommon { - kind: TaskEventKind.AcquiredInput | TaskEventKind.DependsOnStarted | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.End | TaskEventKind.ProblemMatcherEnded | TaskEventKind.ProblemMatcherStarted | TaskEventKind.ProblemMatcherFoundErrors; + kind: TaskEventKind.AcquiredInput | TaskEventKind.DependsOnStarted | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.End | TaskEventKind.ProblemMatcherStarted | TaskEventKind.ProblemMatcherFoundErrors; terminalId: number | undefined; } @@ -1270,7 +1272,7 @@ export namespace TaskEvent { }; } - export function general(kind: TaskEventKind.AcquiredInput | TaskEventKind.DependsOnStarted | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.End | TaskEventKind.ProblemMatcherEnded | TaskEventKind.ProblemMatcherStarted | TaskEventKind.ProblemMatcherFoundErrors, task: Task, terminalId?: number): ITaskGeneralEvent { + export function general(kind: TaskEventKind.AcquiredInput | TaskEventKind.DependsOnStarted | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.End | TaskEventKind.ProblemMatcherStarted | TaskEventKind.ProblemMatcherFoundErrors, task: Task, terminalId?: number): ITaskGeneralEvent { return { ...common(task), kind, @@ -1278,6 +1280,14 @@ export namespace TaskEvent { }; } + export function problemMatcherEnded(task: Task, hasErrors: boolean, terminalId?: number): ITaskProblemMatcherEndedEvent { + return { + ...common(task), + kind: TaskEventKind.ProblemMatcherEnded, + hasErrors, + }; + } + export function changed(): ITaskChangedEvent { return { kind: TaskEventKind.Changed }; } @@ -1381,3 +1391,5 @@ export namespace TaskDefinition { return KeyedTaskIdentifier.create(literal); } } + +export const rerunTaskIcon = registerIcon('rerun-task', Codicon.refresh, nls.localize('rerunTaskIcon', 'View icon of the rerun task.')); export const RerunForActiveTerminalCommandId = 'workbench.action.tasks.rerunForActiveTerminal'; diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts similarity index 100% rename from src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts rename to src/vs/workbench/contrib/tasks/electron-browser/taskService.ts diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 6dcda309192..fd55edd7aba 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -65,6 +65,16 @@ export class RemotePty extends BasePty implements ITerminalChildProcess { }); } + sendSignal(signal: string): void { + if (this._inReplay) { + return; + } + + this._startBarrier.wait().then(_ => { + this._remoteTerminalChannel.sendSignal(this.id, signal); + }); + } + processBinary(e: string): Promise { return this._remoteTerminalChannel.processBinary(this.id, e); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 4a4236d5ee5..08df4db79e3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -6,20 +6,13 @@ import { getFontSnippets } from '../../../../base/browser/fonts.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Schemas } from '../../../../base/common/network.js'; -import { isIOS, isWindows } from '../../../../base/common/platform.js'; import { URI } from '../../../../base/common/uri.js'; -import './media/terminal.css'; -import './media/terminalVoice.css'; -import './media/widgets.css'; -import './media/xterm.css'; import * as nls from '../../../../nls.js'; -import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; -import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { Extensions as DragAndDropExtensions, IDragAndDropContributionRegistry, IDraggedResourceEditorInput } from '../../../../platform/dnd/browser/dnd.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; -import { GeneralShellType, ITerminalLogService, WindowsShellType } from '../../../../platform/terminal/common/terminal.js'; +import { ITerminalLogService } from '../../../../platform/terminal/common/terminal.js'; import { TerminalLogService } from '../../../../platform/terminal/common/terminalLogService.js'; import { registerTerminalPlatformConfiguration } from '../../../../platform/terminal/common/terminalPlatformConfiguration.js'; import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; @@ -27,6 +20,14 @@ import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContaine import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; import { IViewContainersRegistry, IViewsRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../../common/views.js'; +import { ITerminalProfileService, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; +import { registerColors } from '../common/terminalColorRegistry.js'; +import { registerTerminalConfiguration } from '../common/terminalConfiguration.js'; +import { terminalStrings } from '../common/terminalStrings.js'; +import './media/terminal.css'; +import './media/terminalVoice.css'; +import './media/widgets.css'; +import './media/xterm.css'; import { RemoteTerminalBackendContribution } from './remoteTerminalBackend.js'; import { ITerminalConfigurationService, ITerminalEditorService, ITerminalGroupService, ITerminalInstanceService, ITerminalService, TerminalDataTransfers, terminalEditorId } from './terminal.js'; import { registerTerminalActions } from './terminalActions.js'; @@ -43,14 +44,8 @@ import { TerminalMainContribution } from './terminalMainContribution.js'; import { setupTerminalMenus } from './terminalMenus.js'; import { TerminalProfileService } from './terminalProfileService.js'; import { TerminalService } from './terminalService.js'; -import { TerminalViewPane } from './terminalView.js'; -import { ITerminalProfileService, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; -import { registerColors } from '../common/terminalColorRegistry.js'; -import { registerTerminalConfiguration } from '../common/terminalConfiguration.js'; -import { TerminalContextKeyStrings, TerminalContextKeys } from '../common/terminalContextKey.js'; -import { terminalStrings } from '../common/terminalStrings.js'; -import { registerSendSequenceKeybinding } from './terminalKeybindings.js'; import { TerminalTelemetryContribution } from './terminalTelemetry.js'; +import { TerminalViewPane } from './terminalView.js'; // Register services registerSingleton(ITerminalLogService, TerminalLogService, InstantiationType.Delayed); @@ -133,120 +128,8 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews } }], VIEW_CONTAINER); -// Register actions registerTerminalActions(); -const enum Constants { - /** The text representation of `^` is `'A'.charCodeAt(0) + 1`. */ - CtrlLetterOffset = 64 -} - -// An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the -// shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is -// disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen -// reader. This works even when clipboard.readText is not supported. -if (isWindows) { - registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - Constants.CtrlLetterOffset), { // ctrl+v - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - primary: KeyMod.CtrlCmd | KeyCode.KeyV - }); -} - -// Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the -// shell integration script. This allows keystrokes that cannot be sent via VT sequences to work. -// See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007 -registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space (MenuComplete) - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - primary: KeyMod.CtrlCmd | KeyCode.Space, - mac: { primary: KeyMod.WinCtrl | KeyCode.Space } -}); -registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space (SetMark) - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - primary: KeyMod.Alt | KeyCode.Space -}); -registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter (AddLine) - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - primary: KeyMod.Shift | KeyCode.Enter -}); -registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end (SelectLine) - HACK: \x1b[1;2F is supposed to work but it doesn't - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow } -}); - -// Always on pwsh keybindings -registerSendSequenceKeybinding('\x1b[1;2H', { // Shift+home - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell)), - mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.LeftArrow } -}); - -// Map ctrl+alt+r -> ctrl+r when in accessibility mode due to default run recent command keybinding -registerSendSequenceKeybinding('\x12', { - when: ContextKeyExpr.and(TerminalContextKeys.focus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyR, - mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyR } -}); - -// Map ctrl+alt+g -> ctrl+g due to default go to recent directory keybinding -registerSendSequenceKeybinding('\x07', { - when: TerminalContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyG, - mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyG } -}); - -// send ctrl+c to the iPad when the terminal is focused and ctrl+c is pressed to kill the process (work around for #114009) -if (isIOS) { - registerSendSequenceKeybinding(String.fromCharCode('C'.charCodeAt(0) - Constants.CtrlLetterOffset), { // ctrl+c - when: ContextKeyExpr.and(TerminalContextKeys.focus), - primary: KeyMod.WinCtrl | KeyCode.KeyC - }); -} - -// Delete word left: ctrl+w -registerSendSequenceKeybinding(String.fromCharCode('W'.charCodeAt(0) - Constants.CtrlLetterOffset), { - primary: KeyMod.CtrlCmd | KeyCode.Backspace, - mac: { primary: KeyMod.Alt | KeyCode.Backspace } -}); -if (isWindows) { - // Delete word left: ctrl+h - // Windows cmd.exe requires ^H to delete full word left - registerSendSequenceKeybinding(String.fromCharCode('H'.charCodeAt(0) - Constants.CtrlLetterOffset), { - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.CommandPrompt)), - primary: KeyMod.CtrlCmd | KeyCode.Backspace, - }); -} -// Delete word right: alt+d [27, 100] -registerSendSequenceKeybinding('\u001bd', { - primary: KeyMod.CtrlCmd | KeyCode.Delete, - mac: { primary: KeyMod.Alt | KeyCode.Delete } -}); -// Delete to line start: ctrl+u -registerSendSequenceKeybinding('\u0015', { - mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace } -}); -// Move to line start: ctrl+A -registerSendSequenceKeybinding(String.fromCharCode('A'.charCodeAt(0) - 64), { - mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow } -}); -// Move to line end: ctrl+E -registerSendSequenceKeybinding(String.fromCharCode('E'.charCodeAt(0) - 64), { - mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } -}); -// NUL: ctrl+shift+2 -registerSendSequenceKeybinding('\u0000', { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit2, - mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Digit2 } -}); -// RS: ctrl+shift+6 -registerSendSequenceKeybinding('\u001e', { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit6, - mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Digit6 } -}); -// US (Undo): ctrl+/ -registerSendSequenceKeybinding('\u001f', { - primary: KeyMod.CtrlCmd | KeyCode.Slash, - mac: { primary: KeyMod.WinCtrl | KeyCode.Slash } -}); - setupTerminalCommands(); setupTerminalMenus(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 2059d0d68b1..1e49bb74ca9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -899,6 +899,13 @@ export interface ITerminalInstance extends IBaseTerminalInstance { */ sendText(text: string, shouldExecute: boolean, bracketedPasteMode?: boolean): Promise; + /** + * Sends a signal to the terminal instance's process. + * + * @param signal The signal to send (e.g., 'SIGTERM', 'SIGINT', 'SIGKILL'). + */ + sendSignal(signal: string): Promise; + /** * Sends a path to the terminal instance, preparing it as needed based on the detected shell * running within the terminal. The text is written to the stdin of the underlying pty process diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index ff14b2b4af7..8e9863d4dc3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -3,22 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isKeyboardEvent, isMouseEvent, isPointerEvent } from '../../../../base/browser/dom.js'; import { Action } from '../../../../base/common/actions.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; +import { Iterable } from '../../../../base/common/iterator.js'; import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { Schemas } from '../../../../base/common/network.js'; -import { isWindows } from '../../../../base/common/platform.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { isAbsolute } from '../../../../base/common/path.js'; +import { isWindows } from '../../../../base/common/platform.js'; +import { dirname } from '../../../../base/common/resources.js'; import { isObject, isString } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { EndOfLinePreference } from '../../../../editor/common/model.js'; +import { getIconClasses } from '../../../../editor/common/services/getIconClasses.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; import { localize, localize2 } from '../../../../nls.js'; +import { AccessibleViewProviderId } from '../../../../platform/accessibility/browser/accessibleView.js'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; -import { Action2, registerAction2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js'; +import { Action2, IAction2Options, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { FileKind } from '../../../../platform/files/common/files.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; @@ -26,42 +36,31 @@ import { IListService } from '../../../../platform/list/browser/listService.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IPickOptions, IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; +import { TerminalCapability } from '../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalProfile, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId } from '../../../../platform/terminal/common/terminal.js'; +import { createProfileSchemaEnums } from '../../../../platform/terminal/common/terminalProfiles.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from '../../../browser/actions/workspaceCommands.js'; import { CLOSE_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; -import { Direction, ICreateTerminalOptions, IDetachedTerminalInstance, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from './terminal.js'; -import { IRemoteTerminalAttachTarget, ITerminalProfileResolverService, ITerminalProfileService, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; -import { TerminalContextKeys } from '../common/terminalContextKey.js'; -import { createProfileSchemaEnums } from '../../../../platform/terminal/common/terminalProfiles.js'; -import { terminalStrings } from '../common/terminalStrings.js'; import { IConfigurationResolverService } from '../../../services/configurationResolver/common/configurationResolver.js'; +import { ConfigurationResolverExpression } from '../../../services/configurationResolver/common/configurationResolverExpression.js'; +import { editorGroupToColumn } from '../../../services/editor/common/editorGroupColumn.js'; +import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; +import { SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; -import { IHistoryService } from '../../../services/history/common/history.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; -import { SIDE_GROUP } from '../../../services/editor/common/editorService.js'; -import { isAbsolute } from '../../../../base/common/path.js'; -import { ITerminalQuickPickItem } from './terminalProfileQuickpick.js'; -import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { getIconId, getColorClass, getUriClasses } from './terminalIcon.js'; -import { IModelService } from '../../../../editor/common/services/model.js'; -import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { dirname } from '../../../../base/common/resources.js'; -import { getIconClasses } from '../../../../editor/common/services/getIconClasses.js'; -import { FileKind } from '../../../../platform/files/common/files.js'; -import { TerminalCapability } from '../../../../platform/terminal/common/capabilities/capabilities.js'; -import { killTerminalIcon, newTerminalIcon } from './terminalIcons.js'; -import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; -import { Iterable } from '../../../../base/common/iterator.js'; import { accessibleViewCurrentProviderId, accessibleViewIsShown, accessibleViewOnLastLine } from '../../accessibility/browser/accessibilityConfiguration.js'; -import { isKeyboardEvent, isMouseEvent, isPointerEvent } from '../../../../base/browser/dom.js'; -import { editorGroupToColumn } from '../../../services/editor/common/editorGroupColumn.js'; +import { IRemoteTerminalAttachTarget, ITerminalProfileResolverService, ITerminalProfileService, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; +import { TerminalContextKeys } from '../common/terminalContextKey.js'; +import { terminalStrings } from '../common/terminalStrings.js'; +import { Direction, ICreateTerminalOptions, IDetachedTerminalInstance, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from './terminal.js'; import { InstanceContext } from './terminalContextMenu.js'; -import { AccessibleViewProviderId } from '../../../../platform/accessibility/browser/accessibleView.js'; +import { getColorClass, getIconId, getUriClasses } from './terminalIcon.js'; +import { killTerminalIcon, newTerminalIcon } from './terminalIcons.js'; +import { ITerminalQuickPickItem } from './terminalProfileQuickpick.js'; import { TerminalTabList } from './terminalTabsList.js'; -import { ConfigurationResolverExpression } from '../../../services/configurationResolver/common/configurationResolverExpression.js'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -120,23 +119,6 @@ export async function getCwdForSplit( } } -export const terminalSendSequenceCommand = async (accessor: ServicesAccessor, args: unknown) => { - const instance = accessor.get(ITerminalService).activeInstance; - if (instance) { - const text = isObject(args) && 'text' in args ? toOptionalString(args.text) : undefined; - if (!text) { - return; - } - const configurationResolverService = accessor.get(IConfigurationResolverService); - const workspaceContextService = accessor.get(IWorkspaceContextService); - const historyService = accessor.get(IHistoryService); - const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.isRemote ? Schemas.vscodeRemote : Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; - const resolvedText = await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, text); - instance.sendText(resolvedText, false); - } -}; - export class TerminalLaunchHelpAction extends Action { constructor( @@ -891,7 +873,7 @@ export function registerTerminalActions() { { id: MenuId.ViewTitle, group: 'navigation', - order: 4, + order: 5, when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), isHiddenByDefault: true } @@ -954,29 +936,6 @@ export function registerTerminalActions() { } }); - registerTerminalAction({ - id: TerminalCommandId.SendSequence, - title: terminalStrings.sendSequence, - f1: false, - metadata: { - description: terminalStrings.sendSequence.value, - args: [{ - name: 'args', - schema: { - type: 'object', - required: ['text'], - properties: { - text: { - description: localize('sendSequence', "The sequence of text to send to the terminal"), - type: 'string' - } - }, - } - }] - }, - run: (c, accessor, args) => terminalSendSequenceCommand(accessor, args) - }); - registerTerminalAction({ id: TerminalCommandId.NewWithCwd, title: terminalStrings.newWithCwd, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts b/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts index f5f78bd178f..fd222eda77e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts @@ -11,6 +11,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import type { ThemeIcon } from '../../../../base/common/themables.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { getIconRegistry, IconContribution } from '../../../../platform/theme/common/iconRegistry.js'; import { WorkbenchIconSelectBox } from '../../../services/userDataProfile/browser/iconSelectBox.js'; @@ -39,14 +40,14 @@ export class TerminalIconPicker extends Disposable { constructor( @IInstantiationService instantiationService: IInstantiationService, - @IHoverService private readonly _hoverService: IHoverService + @IHoverService private readonly _hoverService: IHoverService, + @ILayoutService private readonly _layoutService: ILayoutService, ) { super(); this._iconSelectBox = instantiationService.createInstance(WorkbenchIconSelectBox, { icons: icons.value, - inputBoxStyles: defaultInputBoxStyles, - showIconInfo: true + inputBoxStyles: defaultInputBoxStyles }); } @@ -58,18 +59,21 @@ export class TerminalIconPicker extends Disposable { this._iconSelectBox.dispose(); })); this._iconSelectBox.clearInput(); + const body = getActiveDocument().body; + const bodyRect = body.getBoundingClientRect(); const hoverWidget = this._hoverService.showInstantHover({ content: this._iconSelectBox.domNode, - target: getActiveDocument().body, + target: { + targetElements: [body], + x: bodyRect.left + (bodyRect.width - dimension.width) / 2, + y: bodyRect.top + this._layoutService.activeContainerOffset.quickPickTop - 2 + }, position: { hoverPosition: HoverPosition.BELOW, }, persistence: { sticky: true, }, - appearance: { - showPointer: true - } }, true); if (hoverWidget) { this._register(hoverWidget); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 165c6c2c346..e17e9980b7f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1317,6 +1317,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + async sendSignal(signal: string): Promise { + this._logService.debug('sending signal (vscode)', signal); + await this._processManager.sendSignal(signal); + } + async sendPath(originalPath: string | URI, shouldExecute: boolean): Promise { return this.sendText(await this.preparePathForShell(originalPath), shouldExecute); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalKeybindings.ts b/src/vs/workbench/contrib/terminal/browser/terminalKeybindings.ts deleted file mode 100644 index 39aad28d17d..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/terminalKeybindings.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js'; -import { IKeybindings, KeybindingWeight, KeybindingsRegistry } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { TerminalCommandId } from '../common/terminal.js'; -import { TerminalContextKeys } from '../common/terminalContextKey.js'; -import { terminalSendSequenceCommand } from './terminalActions.js'; - -export function registerSendSequenceKeybinding(text: string, rule: { when?: ContextKeyExpression } & IKeybindings): void { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: TerminalCommandId.SendSequence, - weight: KeybindingWeight.WorkbenchContrib, - when: rule.when || TerminalContextKeys.focus, - primary: rule.primary, - mac: rule.mac, - linux: rule.linux, - win: rule.win, - handler: terminalSendSequenceCommand, - args: { text } - }); -} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 54acb778385..0317f88ef39 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -473,7 +473,7 @@ export function setupTerminalMenus(): void { icon: Codicon.clearAll }, group: 'navigation', - order: 4, + order: 6, when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), isHiddenByDefault: true } @@ -487,7 +487,7 @@ export function setupTerminalMenus(): void { icon: Codicon.run }, group: 'navigation', - order: 5, + order: 7, when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), isHiddenByDefault: true } @@ -501,7 +501,7 @@ export function setupTerminalMenus(): void { icon: Codicon.selection }, group: 'navigation', - order: 6, + order: 8, when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), isHiddenByDefault: true } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index ade719bd981..cd71fa1cd39 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -120,6 +120,10 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal this._onInput.fire(data); } + sendSignal(signal: string): void { + // No-op - Extension terminals don't have direct process access + } + resize(cols: number, rows: number): void { this._onResize.fire({ cols, rows }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 535c7ad5562..8bba0827b1f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -609,6 +609,13 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } } + async sendSignal(signal: string): Promise { + await this.ptyProcessReady; + if (this._process) { + this._process.sendSignal(signal); + } + } + async processBinary(data: string): Promise { await this.ptyProcessReady; this._dataFilter.disableSeamlessRelaunch(); diff --git a/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts index 5aeedf779f7..532d9f1c1ce 100644 --- a/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts @@ -216,6 +216,9 @@ export class RemoteTerminalChannelClient implements IPtyHostController { input(id: number, data: string): Promise { return this._channel.call(RemoteTerminalChannelRequest.Input, [id, data]); } + sendSignal(id: number, signal: string): Promise { + return this._channel.call(RemoteTerminalChannelRequest.SendSignal, [id, signal]); + } acknowledgeDataEvent(id: number, charCount: number): Promise { return this._channel.call(RemoteTerminalChannelRequest.AcknowledgeDataEvent, [id, charCount]); } diff --git a/src/vs/workbench/contrib/terminal/common/remote/terminal.ts b/src/vs/workbench/contrib/terminal/common/remote/terminal.ts index 5e649e86a51..16af3dcf8b8 100644 --- a/src/vs/workbench/contrib/terminal/common/remote/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/remote/terminal.ts @@ -69,6 +69,7 @@ export const enum RemoteTerminalChannelRequest { AcceptPtyHostResolvedVariables = '$acceptPtyHostResolvedVariables', Start = '$start', Input = '$input', + SendSignal = '$sendSignal', AcknowledgeDataEvent = '$acknowledgeDataEvent', Shutdown = '$shutdown', Resize = '$resize', diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1 similarity index 85% rename from src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 rename to src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1 index 4c3dbf35a94..b8411f694ac 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.psm1 @@ -4,7 +4,7 @@ # --------------------------------------------------------------------------------------------- # Prevent installing more than once per session -if (Test-Path variable:global:__VSCodeOriginalPrompt) { +if (Test-Path variable:Script:__VSCodeState.OriginalPrompt) { return; } @@ -13,27 +13,33 @@ if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") { return; } -$Global:__VSCodeOriginalPrompt = $function:Prompt - -$Global:__LastHistoryId = -1 -$Global:__VSCodeIsInExecution = $false +$Script:__VSCodeState = @{ + OriginalPrompt = $function:Prompt + LastHistoryId = -1 + IsInExecution = $false + EnvVarsToReport = @() + Nonce = $null + IsStable = $null + IsWindows10 = $false +} # Store the nonce in script scope and unset the global -$Nonce = $env:VSCODE_NONCE +$Script:__VSCodeState.Nonce = $env:VSCODE_NONCE $env:VSCODE_NONCE = $null -$isStable = $env:VSCODE_STABLE +$Script:__VSCodeState.IsStable = $env:VSCODE_STABLE $env:VSCODE_STABLE = $null $__vscode_shell_env_reporting = $env:VSCODE_SHELL_ENV_REPORTING $env:VSCODE_SHELL_ENV_REPORTING = $null -$Global:envVarsToReport = @() if ($__vscode_shell_env_reporting) { - $Global:envVarsToReport = $__vscode_shell_env_reporting.Split(',') + $Script:__VSCodeState.EnvVarsToReport = $__vscode_shell_env_reporting.Split(',') } +Remove-Variable -Name __vscode_shell_env_reporting -ErrorAction SilentlyContinue $osVersion = [System.Environment]::OSVersion.Version -$isWindows10 = $IsWindows -and $osVersion.Major -eq 10 -and $osVersion.Minor -eq 0 -and $osVersion.Build -lt 22000 +$Script:__VSCodeState.IsWindows10 = $IsWindows -and $osVersion.Major -eq 10 -and $osVersion.Minor -eq 0 -and $osVersion.Build -lt 22000 +Remove-Variable -Name osVersion -ErrorAction SilentlyContinue if ($env:VSCODE_ENV_REPLACE) { $Split = $env:VSCODE_ENV_REPLACE.Split(":") @@ -80,9 +86,9 @@ function Global:Prompt() { $Result = "" # Skip finishing the command if the first command has not yet started or an execution has not # yet begun - if ($Global:__LastHistoryId -ne -1 -and $Global:__VSCodeIsInExecution -eq $true) { - $Global:__VSCodeIsInExecution = $false - if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) { + if ($Script:__VSCodeState.LastHistoryId -ne -1 -and $Script:__VSCodeState.IsInExecution -eq $true) { + $Script:__VSCodeState.IsInExecution = $false + if ($LastHistoryEntry.Id -eq $Script:__VSCodeState.LastHistoryId) { # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command) $Result += "$([char]0x1b)]633;D`a" } @@ -101,15 +107,15 @@ function Global:Prompt() { # Send current environment variables as JSON # OSC 633 ; EnvJson ; ; - if ($Global:envVarsToReport.Count -gt 0) { + if ($Script:__VSCodeState.EnvVarsToReport.Count -gt 0) { $envMap = @{} - foreach ($varName in $envVarsToReport) { + foreach ($varName in $Script:__VSCodeState.EnvVarsToReport) { if (Test-Path "env:$varName") { $envMap[$varName] = (Get-Item "env:$varName").Value } } $envJson = $envMap | ConvertTo-Json -Compress - $Result += "$([char]0x1b)]633;EnvJson;$(__VSCode-Escape-Value $envJson);$Nonce`a" + $Result += "$([char]0x1b)]633;EnvJson;$(__VSCode-Escape-Value $envJson);$($Script:__VSCodeState.Nonce)`a" } # Before running the original prompt, put $? back to what it was: @@ -117,18 +123,18 @@ function Global:Prompt() { Write-Error "failure" -ea ignore } # Run the original prompt - $OriginalPrompt += $Global:__VSCodeOriginalPrompt.Invoke() + $OriginalPrompt += $Script:__VSCodeState.OriginalPrompt.Invoke() $Result += $OriginalPrompt # Prompt # OSC 633 ; = ST - if ($isStable -eq "0") { + if ($Script:__VSCodeState.IsStable -eq "0") { $Result += "$([char]0x1b)]633;P;Prompt=$(__VSCode-Escape-Value $OriginalPrompt)`a" } # Write command started $Result += "$([char]0x1b)]633;B`a" - $Global:__LastHistoryId = $LastHistoryEntry.Id + $Script:__VSCodeState.LastHistoryId = $LastHistoryEntry.Id return $Result } @@ -148,10 +154,10 @@ elseif ((Test-Path variable:global:GitPromptSettings) -and $Global:GitPromptSett if (Get-Module -Name PSReadLine) { [Console]::Write("$([char]0x1b)]633;P;HasRichCommandDetection=True`a") - $__VSCodeOriginalPSConsoleHostReadLine = $function:PSConsoleHostReadLine + $Script:__VSCodeState.OriginalPSConsoleHostReadLine = $function:PSConsoleHostReadLine function Global:PSConsoleHostReadLine { - $CommandLine = $__VSCodeOriginalPSConsoleHostReadLine.Invoke() - $Global:__VSCodeIsInExecution = $true + $CommandLine = $Script:__VSCodeState.OriginalPSConsoleHostReadLine.Invoke() + $Script:__VSCodeState.IsInExecution = $true # Command line # OSC 633 ; E [; [; ]] ST @@ -159,8 +165,8 @@ if (Get-Module -Name PSReadLine) { $Result += $(__VSCode-Escape-Value $CommandLine) # Only send the nonce if the OS is not Windows 10 as it seems to echo to the terminal # sometimes - if ($IsWindows10 -eq $false) { - $Result += ";$Nonce" + if ($Script:__VSCodeState.IsWindows10 -eq $false) { + $Result += ";$($Script:__VSCodeState.Nonce)" } $Result += "`a" @@ -175,9 +181,9 @@ if (Get-Module -Name PSReadLine) { } # Set ContinuationPrompt property - $ContinuationPrompt = (Get-PSReadLineOption).ContinuationPrompt - if ($ContinuationPrompt) { - [Console]::Write("$([char]0x1b)]633;P;ContinuationPrompt=$(__VSCode-Escape-Value $ContinuationPrompt)`a") + $Script:__VSCodeState.ContinuationPrompt = (Get-PSReadLineOption).ContinuationPrompt + if ($Script:__VSCodeState.ContinuationPrompt) { + [Console]::Write("$([char]0x1b)]633;P;ContinuationPrompt=$(__VSCode-Escape-Value $Script:__VSCodeState.ContinuationPrompt)`a") } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 3683a60a46b..6951d80a2bd 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -296,6 +296,7 @@ export interface ITerminalProcessManager extends IDisposable, ITerminalProcessIn createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): Promise; relaunch(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, reset: boolean): Promise; write(data: string): Promise; + sendSignal(signal: string): Promise; setDimensions(cols: number, rows: number): Promise; setDimensions(cols: number, rows: number, sync: false): Promise; setDimensions(cols: number, rows: number, sync: true): void; @@ -473,6 +474,7 @@ export const enum TerminalCommandId { SelectToPreviousLine = 'workbench.action.terminal.selectToPreviousLine', SelectToNextLine = 'workbench.action.terminal.selectToNextLine', SendSequence = 'workbench.action.terminal.sendSequence', + SendSignal = 'workbench.action.terminal.sendSignal', AttachToSession = 'workbench.action.terminal.attachToSession', DetachSession = 'workbench.action.terminal.detachSession', MoveToEditor = 'workbench.action.terminal.moveToEditor', diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index ed00de1f363..5251fc9527a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -36,7 +36,6 @@ export const terminalStrings = { rename: localize2('workbench.action.terminal.rename', "Rename..."), toggleSizeToContentWidth: localize2('workbench.action.terminal.sizeToContentWidthInstance', "Toggle Size to Content Width"), focusHover: localize2('workbench.action.terminal.focusHover', "Focus Hover"), - sendSequence: localize2('workbench.action.terminal.sendSequence', "Send Custom Sequence to Terminal"), newWithCwd: localize2('workbench.action.terminal.newWithCwd', "Create New Terminal Starting in a Custom Working Directory"), renameWithArgs: localize2('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"), scrollToPreviousCommand: localize2('workbench.action.terminal.scrollToPreviousCommand', "Scroll to Previous Command"), diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-browser/localPty.ts similarity index 95% rename from src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts rename to src/vs/workbench/contrib/terminal/electron-browser/localPty.ts index ed397a35984..5375e636116 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/localPty.ts @@ -45,6 +45,13 @@ export class LocalPty extends BasePty implements ITerminalChildProcess { this._proxy.input(this.id, data); } + sendSignal(signal: string): void { + if (this._inReplay) { + return; + } + this._proxy.sendSignal(this.id, signal); + } + resize(cols: number, rows: number): void { if (this._inReplay || this._lastDimensions.cols === cols && this._lastDimensions.rows === rows) { return; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts similarity index 99% rename from src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts rename to src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts index 2c93a4c5c8b..0be06471a89 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts @@ -20,7 +20,7 @@ import { ITerminalProfileResolverService } from '../common/terminal.js'; import { TerminalStorageKeys } from '../common/terminalStorageKeys.js'; import { LocalPty } from './localPty.js'; import { IConfigurationResolverService } from '../../../services/configurationResolver/common/configurationResolver.js'; -import { IShellEnvironmentService } from '../../../services/environment/electron-sandbox/shellEnvironmentService.js'; +import { IShellEnvironmentService } from '../../../services/environment/electron-browser/shellEnvironmentService.js'; import { IHistoryService } from '../../../services/history/common/history.js'; import * as terminalEnvironment from '../common/terminalEnvironment.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; @@ -28,7 +28,7 @@ import { IEnvironmentVariableService } from '../common/environmentVariable.js'; import { BaseTerminalBackend } from '../browser/baseTerminalBackend.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { Client as MessagePortClient } from '../../../../base/parts/ipc/common/ipc.mp.js'; -import { acquirePort } from '../../../../base/parts/ipc/electron-sandbox/ipc.mp.js'; +import { acquirePort } from '../../../../base/parts/ipc/electron-browser/ipc.mp.js'; import { getDelayedChannel, ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { mark, PerformanceMark } from '../../../../base/common/performance.js'; import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts similarity index 97% rename from src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts rename to src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index 381db45fa4a..b439e425bd3 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ILocalPtyService, TerminalIpcChannels } from '../../../../platform/terminal/common/terminal.js'; import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from '../../../common/contributions.js'; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts similarity index 99% rename from src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts rename to src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts index c3132312b08..a076cf8cfd9 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcRenderer } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ipcRenderer } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { INativeOpenFileRequest } from '../../../../platform/window/common/window.js'; import { URI } from '../../../../base/common/uri.js'; import { IFileService } from '../../../../platform/files/common/files.js'; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalProfileResolverService.ts similarity index 100% rename from src/vs/workbench/contrib/terminal/electron-sandbox/terminalProfileResolverService.ts rename to src/vs/workbench/contrib/terminal/electron-browser/terminalProfileResolverService.ts diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalRemote.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts similarity index 100% rename from src/vs/workbench/contrib/terminal/electron-sandbox/terminalRemote.ts rename to src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts diff --git a/src/vs/workbench/contrib/terminal/terminal.all.ts b/src/vs/workbench/contrib/terminal/terminal.all.ts index 69ba6694072..5189ed4a3a2 100644 --- a/src/vs/workbench/contrib/terminal/terminal.all.ts +++ b/src/vs/workbench/contrib/terminal/terminal.all.ts @@ -27,6 +27,8 @@ import '../terminalContrib/stickyScroll/browser/terminal.stickyScroll.contributi import '../terminalContrib/quickAccess/browser/terminal.quickAccess.contribution.js'; import '../terminalContrib/quickFix/browser/terminal.quickFix.contribution.js'; import '../terminalContrib/typeAhead/browser/terminal.typeAhead.contribution.js'; +import '../terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.js'; +import '../terminalContrib/sendSignal/browser/terminal.sendSignal.contribution.js'; import '../terminalContrib/suggest/browser/terminal.suggest.contribution.js'; import '../terminalContrib/chat/browser/terminal.initialHint.contribution.js'; import '../terminalContrib/wslRecommendation/browser/terminal.wslRecommendation.contribution.js'; diff --git a/src/vs/workbench/contrib/terminal/terminalContribExports.ts b/src/vs/workbench/contrib/terminal/terminalContribExports.ts index 665aa7bc3f9..95b3902bffa 100644 --- a/src/vs/workbench/contrib/terminal/terminalContribExports.ts +++ b/src/vs/workbench/contrib/terminal/terminalContribExports.ts @@ -30,8 +30,8 @@ export const enum TerminalContribCommandId { // soft layer breakers between `terminal/` and `terminalContrib/` but there are difficulties in // removing the dependency. These are explicitly defined here to avoid an eslint line override. export const enum TerminalContribSettingId { - SuggestEnabled = TerminalSuggestSettingId.Enabled, StickyScrollEnabled = TerminalStickyScrollSettingId.Enabled, + SuggestEnabled = TerminalSuggestSettingId.Enabled, } // Export configuration schemes from terminalContrib - this is an exception to the eslint rule since diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 5a8f8cddbdc..11a0f0cec68 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -83,6 +83,7 @@ class TestTerminalChildProcess extends Disposable implements ITerminalChildProce async start(): Promise { return undefined; } shutdown(immediate: boolean): void { } input(data: string): void { } + sendSignal(signal: string): void { } resize(cols: number, rows: number): void { } clearBuffer(): void { } acknowledgeDataEvent(charCount: number): void { } diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts index 85af56a0c6e..ec63876cd46 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts @@ -39,6 +39,7 @@ class TestTerminalChildProcess implements ITerminalChildProcess { async start(): Promise { return undefined; } shutdown(immediate: boolean): void { } input(data: string): void { } + sendSignal(signal: string): void { } resize(cols: number, rows: number): void { } clearBuffer(): void { } acknowledgeDataEvent(charCount: number): void { } diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index 067736bd947..0ad81a5b126 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -75,7 +75,7 @@ registerTerminalAction({ } const data = await quickInputService.input({ value: '', - placeHolder: 'Enter data, use \\x to escape', + placeHolder: 'Enter data (supports \\n, \\r, \\xAB)', prompt: localize('workbench.action.terminal.writeDataToTerminal.prompt', "Enter data to write directly to the terminal, bypassing the pty"), }); if (!data) { diff --git a/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts b/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts index f0707160e52..8e77a5d9845 100644 --- a/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; -import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { localize2 } from '../../../../../nls.js'; import { AccessibleViewProviderId } from '../../../../../platform/accessibility/browser/accessibleView.js'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../platform/accessibility/common/accessibility.js'; +import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService, type IContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -17,6 +18,7 @@ import { accessibleViewCurrentProviderId, accessibleViewIsShown } from '../../.. import type { ITerminalContribution, ITerminalInstance } from '../../../terminal/browser/terminal.js'; import { registerActiveInstanceAction, registerTerminalAction } from '../../../terminal/browser/terminalActions.js'; import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; +import { TERMINAL_VIEW_ID } from '../../../terminal/common/terminal.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { clearShellFileHistory, getCommandHistory, getDirectoryHistory } from '../common/history.js'; import { TerminalHistoryCommandId } from '../common/terminal.history.js'; @@ -115,6 +117,15 @@ registerActiveInstanceAction({ when: TerminalContextKeys.focus, weight: KeybindingWeight.WorkbenchContrib }, + menu: [ + { + id: MenuId.ViewTitle, + group: 'shellIntegration', + order: 0, + when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + isHiddenByDefault: true + } + ], run: async (activeInstance, c) => { const history = TerminalHistoryContribution.get(activeInstance); if (!history) { @@ -129,7 +140,7 @@ registerActiveInstanceAction({ } }); -registerActiveInstanceAction({ +registerTerminalAction({ id: TerminalHistoryCommandId.RunRecentCommand, title: localize2('workbench.action.terminal.runRecentCommand', 'Run Recent Command...'), precondition, @@ -146,7 +157,31 @@ registerActiveInstanceAction({ weight: KeybindingWeight.WorkbenchContrib } ], - run: async (activeInstance, c) => { + menu: [ + { + id: MenuId.ViewTitle, + group: 'shellIntegration', + order: 1, + when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + isHiddenByDefault: true + } + ], + run: async (c, accessor) => { + let activeInstance = c.service.activeInstance; + // If an instanec doesn't exist, create one and wait for shell type to be set + if (!activeInstance) { + const newInstance = activeInstance = await c.service.getActiveOrCreateInstance(); + await c.service.revealActiveTerminal(); + const store = new DisposableStore(); + const wasDisposedPrematurely = await new Promise(r => { + store.add(newInstance.onDidChangeShellType(() => r(false))); + store.add(newInstance.onDisposed(() => r(true))); + }); + store.dispose(); + if (wasDisposedPrematurely) { + return; + } + } const history = TerminalHistoryContribution.get(activeInstance); if (!history) { return; diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts index d2e7a0ee776..4305d050846 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts @@ -17,7 +17,7 @@ import { IWorkspaceContextService } from '../../../../../../platform/workspace/c import { CommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/commandDetectionCapability.js'; import { TerminalBuiltinLinkType } from '../../browser/links.js'; import { TerminalLocalFileLinkOpener, TerminalLocalFolderInWorkspaceLinkOpener, TerminalSearchLinkOpener } from '../../browser/terminalLinkOpeners.js'; -import { TerminalCapability, IXtermMarker } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { TerminalCapabilityStore } from '../../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; @@ -29,6 +29,7 @@ import { ITerminalLogService } from '../../../../../../platform/terminal/common/ import { importAMDNodeModule } from '../../../../../../amdX.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { TerminalCommand } from '../../../../../../platform/terminal/common/capabilities/commandDetection/terminalCommand.js'; +import type { IMarker } from '@xterm/headless'; interface ITerminalLinkActivationResult { source: 'editor' | 'search'; @@ -148,7 +149,7 @@ suite('Workbench - TerminalLinkOpeners', () => { startX: undefined, marker: { line: 0 - } as Partial as any, + } as Partial as any, })]); fileService.setFiles([ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), @@ -287,7 +288,7 @@ suite('Workbench - TerminalLinkOpeners', () => { startX: undefined, marker: { line: 0 - } as Partial as any, + } as Partial as any, exitCode: 0, commandStartLineContent: '', markProperties: {} @@ -549,7 +550,7 @@ suite('Workbench - TerminalLinkOpeners', () => { duration: 0, marker: { line: 0 - } as Partial as any, + } as Partial as any, })]); await opener.open({ text: 'file.txt', diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts index 55ff138f7ff..4a095d55e76 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts @@ -6,7 +6,6 @@ import { Emitter } from '../../../../../base/common/event.js'; import { IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../nls.js'; -import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITerminalCommandSelector } from '../../../../../platform/terminal/common/terminal.js'; import { ITerminalQuickFixService, ITerminalQuickFixProvider, ITerminalQuickFixProviderSelector } from './quickFix.js'; import { isProposedApiEnabled } from '../../../../services/extensions/common/extensions.js'; @@ -20,6 +19,8 @@ export class TerminalQuickFixService implements ITerminalQuickFixService { private _providers: Map = new Map(); get providers(): Map { return this._providers; } + private _pendingProviders: Map = new Map(); + private readonly _onDidRegisterProvider = new Emitter(); readonly onDidRegisterProvider = this._onDidRegisterProvider.event; private readonly _onDidRegisterCommandSelector = new Emitter(); @@ -29,9 +30,7 @@ export class TerminalQuickFixService implements ITerminalQuickFixService { readonly extensionQuickFixes: Promise>; - constructor( - @ILogService private readonly _logService: ILogService, - ) { + constructor() { this.extensionQuickFixes = new Promise((r) => quickFixExtensionPoint.setHandler(fixes => { r(fixes.filter(c => isProposedApiEnabled(c.description, 'terminalQuickFixProvider')).map(c => { if (!c.value) { @@ -50,6 +49,14 @@ export class TerminalQuickFixService implements ITerminalQuickFixService { registerCommandSelector(selector: ITerminalCommandSelector): void { this._selectors.set(selector.id, selector); this._onDidRegisterCommandSelector.fire(selector); + + // Check if there's a pending provider for this selector + const pendingProvider = this._pendingProviders.get(selector.id); + if (pendingProvider) { + this._pendingProviders.delete(selector.id); + this._providers.set(selector.id, pendingProvider); + this._onDidRegisterProvider.fire({ selector, provider: pendingProvider }); + } } registerQuickFixProvider(id: string, provider: ITerminalQuickFixProvider): IDisposable { @@ -61,17 +68,20 @@ export class TerminalQuickFixService implements ITerminalQuickFixService { if (disposed) { return; } - this._providers.set(id, provider); const selector = this._selectors.get(id); - if (!selector) { - this._logService.error(`No registered selector for ID: ${id}`); - return; + if (selector) { + // Selector is already available, register immediately + this._providers.set(id, provider); + this._onDidRegisterProvider.fire({ selector, provider }); + } else { + // Selector not yet available, store provider as pending + this._pendingProviders.set(id, provider); } - this._onDidRegisterProvider.fire({ selector, provider }); }); return toDisposable(() => { disposed = true; this._providers.delete(id); + this._pendingProviders.delete(id); const selector = this._selectors.get(id); if (selector) { this._selectors.delete(id); diff --git a/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts b/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts new file mode 100644 index 00000000000..ee7f21b223c --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts @@ -0,0 +1,226 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { Schemas } from '../../../../../base/common/network.js'; +import { isIOS, isWindows } from '../../../../../base/common/platform.js'; +import { isObject, isString } from '../../../../../base/common/types.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../platform/accessibility/common/accessibility.js'; +import { ContextKeyExpr, type ContextKeyExpression } from '../../../../../platform/contextkey/common/contextkey.js'; +import type { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingsRegistry, KeybindingWeight, type IKeybindings } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; +import { GeneralShellType, WindowsShellType } from '../../../../../platform/terminal/common/terminal.js'; +import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { IConfigurationResolverService } from '../../../../services/configurationResolver/common/configurationResolver.js'; +import { IHistoryService } from '../../../../services/history/common/history.js'; +import { ITerminalService } from '../../../terminal/browser/terminal.js'; +import { registerTerminalAction } from '../../../terminal/browser/terminalActions.js'; +import { TerminalContextKeys, TerminalContextKeyStrings } from '../../../terminal/common/terminalContextKey.js'; + +export const enum TerminalSendSequenceCommandId { + SendSequence = 'workbench.action.terminal.sendSequence', +} + +function toOptionalString(obj: unknown): string | undefined { + return isString(obj) ? obj : undefined; +} + +export const terminalSendSequenceCommand = async (accessor: ServicesAccessor, args: unknown) => { + const quickInputService = accessor.get(IQuickInputService); + const configurationResolverService = accessor.get(IConfigurationResolverService); + const workspaceContextService = accessor.get(IWorkspaceContextService); + const historyService = accessor.get(IHistoryService); + const terminalService = accessor.get(ITerminalService); + + const instance = terminalService.activeInstance; + if (instance) { + let text = isObject(args) && 'text' in args ? toOptionalString(args.text) : undefined; + + // If no text provided, prompt user for input and process special characters + if (!text) { + text = await quickInputService.input({ + value: '', + placeHolder: 'Enter sequence to send (supports \\n, \\r, \\xAB)', + prompt: localize('workbench.action.terminal.sendSequence.prompt', "Enter sequence to send to the terminal"), + }); + if (!text) { + return; + } + // Process escape sequences + let processedText = text + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r'); + + // Process hex escape sequences (\xNN) + while (true) { + const match = processedText.match(/\\x([0-9a-fA-F]{2})/); + if (match === null || match.index === undefined || match.length < 2) { + break; + } + processedText = processedText.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + processedText.slice(match.index + 4); + } + + text = processedText; + } + + const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.isRemote ? Schemas.vscodeRemote : Schemas.file); + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; + const resolvedText = await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, text); + instance.sendText(resolvedText, false); + } +}; + +const sendSequenceString = localize2('sendSequence', "Send Sequence"); +registerTerminalAction({ + id: TerminalSendSequenceCommandId.SendSequence, + title: sendSequenceString, + f1: true, + metadata: { + description: sendSequenceString.value, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['text'], + properties: { + text: { + description: localize('sendSequence.text.desc', "The sequence of text to send to the terminal"), + type: 'string' + } + }, + } + }] + }, + run: (c, accessor, args) => terminalSendSequenceCommand(accessor, args) +}); + +export function registerSendSequenceKeybinding(text: string, rule: { when?: ContextKeyExpression } & IKeybindings): void { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TerminalSendSequenceCommandId.SendSequence, + weight: KeybindingWeight.WorkbenchContrib, + when: rule.when || TerminalContextKeys.focus, + primary: rule.primary, + mac: rule.mac, + linux: rule.linux, + win: rule.win, + handler: terminalSendSequenceCommand, + args: { text } + }); +} + + + +const enum Constants { + /** The text representation of `^` is `'A'.charCodeAt(0) + 1`. */ + CtrlLetterOffset = 64 +} + +// An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the +// shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is +// disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen +// reader. This works even when clipboard.readText is not supported. +if (isWindows) { + registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - Constants.CtrlLetterOffset), { // ctrl+v + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.CtrlCmd | KeyCode.KeyV + }); +} + +// Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the +// shell integration script. This allows keystrokes that cannot be sent via VT sequences to work. +// See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007 +registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space (MenuComplete) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.CtrlCmd | KeyCode.Space, + mac: { primary: KeyMod.WinCtrl | KeyCode.Space } +}); +registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space (SetMark) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.Alt | KeyCode.Space +}); +registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter (AddLine) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.Shift | KeyCode.Enter +}); +registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end (SelectLine) - HACK: \x1b[1;2F is supposed to work but it doesn't + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow } +}); + +// Always on pwsh keybindings +registerSendSequenceKeybinding('\x1b[1;2H', { // Shift+home + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, GeneralShellType.PowerShell)), + mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.LeftArrow } +}); + +// Map ctrl+alt+r -> ctrl+r when in accessibility mode due to default run recent command keybinding +registerSendSequenceKeybinding('\x12', { + when: ContextKeyExpr.and(TerminalContextKeys.focus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyR, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyR } +}); + +// Map ctrl+alt+g -> ctrl+g due to default go to recent directory keybinding +registerSendSequenceKeybinding('\x07', { + when: TerminalContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyG, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyG } +}); + +// send ctrl+c to the iPad when the terminal is focused and ctrl+c is pressed to kill the process (work around for #114009) +if (isIOS) { + registerSendSequenceKeybinding(String.fromCharCode('C'.charCodeAt(0) - Constants.CtrlLetterOffset), { // ctrl+c + when: ContextKeyExpr.and(TerminalContextKeys.focus), + primary: KeyMod.WinCtrl | KeyCode.KeyC + }); +} + +// Delete word left: ctrl+w +registerSendSequenceKeybinding(String.fromCharCode('W'.charCodeAt(0) - Constants.CtrlLetterOffset), { + primary: KeyMod.CtrlCmd | KeyCode.Backspace, + mac: { primary: KeyMod.Alt | KeyCode.Backspace } +}); +if (isWindows) { + // Delete word left: ctrl+h + // Windows cmd.exe requires ^H to delete full word left + registerSendSequenceKeybinding(String.fromCharCode('H'.charCodeAt(0) - Constants.CtrlLetterOffset), { + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.CommandPrompt)), + primary: KeyMod.CtrlCmd | KeyCode.Backspace, + }); +} +// Delete word right: alt+d [27, 100] +registerSendSequenceKeybinding('\u001bd', { + primary: KeyMod.CtrlCmd | KeyCode.Delete, + mac: { primary: KeyMod.Alt | KeyCode.Delete } +}); +// Delete to line start: ctrl+u +registerSendSequenceKeybinding('\u0015', { + mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace } +}); +// Move to line start: ctrl+A +registerSendSequenceKeybinding(String.fromCharCode('A'.charCodeAt(0) - 64), { + mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow } +}); +// Move to line end: ctrl+E +registerSendSequenceKeybinding(String.fromCharCode('E'.charCodeAt(0) - 64), { + mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } +}); +// NUL: ctrl+shift+2 +registerSendSequenceKeybinding('\u0000', { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit2, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Digit2 } +}); +// RS: ctrl+shift+6 +registerSendSequenceKeybinding('\u001e', { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit6, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Digit6 } +}); +// US (Undo): ctrl+/ +registerSendSequenceKeybinding('\u001f', { + primary: KeyMod.CtrlCmd | KeyCode.Slash, + mac: { primary: KeyMod.WinCtrl | KeyCode.Slash } +}); diff --git a/src/vs/workbench/contrib/terminalContrib/sendSignal/browser/terminal.sendSignal.contribution.ts b/src/vs/workbench/contrib/terminalContrib/sendSignal/browser/terminal.sendSignal.contribution.ts new file mode 100644 index 00000000000..5891e34fdb6 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/sendSignal/browser/terminal.sendSignal.contribution.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isWindows } from '../../../../../base/common/platform.js'; +import { isObject, isString } from '../../../../../base/common/types.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { IQuickInputService, type QuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; +import { registerTerminalAction } from '../../../terminal/browser/terminalActions.js'; + +export const enum TerminalSendSignalCommandId { + SendSignal = 'workbench.action.terminal.sendSignal', +} + +function toOptionalString(obj: unknown): string | undefined { + return isString(obj) ? obj : undefined; +} + +const sendSignalString = localize2('sendSignal', "Send Signal"); +registerTerminalAction({ + id: TerminalSendSignalCommandId.SendSignal, + title: sendSignalString, + f1: !isWindows, + metadata: { + description: sendSignalString.value, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['signal'], + properties: { + signal: { + description: localize('sendSignal.signal.desc', "The signal to send to the terminal process (e.g., 'SIGTERM', 'SIGINT', 'SIGKILL')"), + type: 'string' + } + }, + } + }] + }, + run: async (c, accessor, args) => { + const quickInputService = accessor.get(IQuickInputService); + const instance = c.service.activeInstance; + if (!instance) { + return; + } + + let signal = isObject(args) && 'signal' in args ? toOptionalString(args.signal) : undefined; + + if (!signal) { + const signalOptions: QuickPickItem[] = [ + { label: 'SIGINT', description: localize('SIGINT', 'Interrupt process (Ctrl+C)') }, + { label: 'SIGTERM', description: localize('SIGTERM', 'Terminate process gracefully') }, + { label: 'SIGKILL', description: localize('SIGKILL', 'Force kill process') }, + { label: 'SIGSTOP', description: localize('SIGSTOP', 'Stop process') }, + { label: 'SIGCONT', description: localize('SIGCONT', 'Continue process') }, + { label: 'SIGHUP', description: localize('SIGHUP', 'Hangup') }, + { label: 'SIGQUIT', description: localize('SIGQUIT', 'Quit process') }, + { label: 'SIGUSR1', description: localize('SIGUSR1', 'User-defined signal 1') }, + { label: 'SIGUSR2', description: localize('SIGUSR2', 'User-defined signal 2') }, + { type: 'separator' }, + { label: localize('manualSignal', 'Manually enter signal') } + ]; + + const selected = await quickInputService.pick(signalOptions, { + placeHolder: localize('selectSignal', 'Select signal to send to terminal process') + }); + + if (!selected) { + return; + } + + if (selected.label === localize('manualSignal', 'Manually enter signal')) { + const inputSignal = await quickInputService.input({ + prompt: localize('enterSignal', 'Enter signal name (e.g., SIGTERM, SIGKILL)'), + }); + + if (!inputSignal) { + return; + } + + signal = inputSignal; + } else { + signal = selected.label; + } + } + + await instance.sendSignal(signal); + } +}); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionItem.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionItem.ts index a83e482461f..0ce0c96cf69 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionItem.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionItem.ts @@ -17,6 +17,8 @@ export enum TerminalCompletionItemKind { Option = 5, OptionValue = 6, Flag = 7, + SymbolicLinkFile = 8, + SymbolicLinkFolder = 9, // Kinds only for core InlineSuggestion = 100, InlineSuggestionAlwaysOnTop = 101, diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionModel.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionModel.ts index 82c086f4e82..5caddc89357 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionModel.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionModel.ts @@ -81,6 +81,23 @@ const compareCompletionsFn = (leadingLineContent: string, a: TerminalCompletionI } } + // Boost main and master branches for git commands + // HACK: Currently this just matches leading line content, it should eventually check the + // completion type is a branch + if (a.completion.kind === TerminalCompletionItemKind.Argument && b.completion.kind === TerminalCompletionItemKind.Argument && /^\s*git\b/.test(leadingLineContent)) { + const aLabel = typeof a.completion.label === 'string' ? a.completion.label : a.completion.label.label; + const bLabel = typeof b.completion.label === 'string' ? b.completion.label : b.completion.label.label; + const aIsMainOrMaster = aLabel === 'main' || aLabel === 'master'; + const bIsMainOrMaster = bLabel === 'main' || bLabel === 'master'; + + if (aIsMainOrMaster && !bIsMainOrMaster) { + return -1; + } + if (bIsMainOrMaster && !aIsMainOrMaster) { + return 1; + } + } + // Sort by more detailed completions if (a.completion.kind === TerminalCompletionItemKind.Method && b.completion.kind === TerminalCompletionItemKind.Method) { if (typeof a.completion.label !== 'string' && a.completion.label.description && typeof b.completion.label !== 'string' && b.completion.label.description) { diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index b54bcd1af4d..a9b9500381d 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -11,12 +11,13 @@ import { IConfigurationService } from '../../../../../platform/configuration/com import { IFileService } from '../../../../../platform/files/common/files.js'; import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; import { TerminalCapability, type ITerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; -import { GeneralShellType, TerminalShellType } from '../../../../../platform/terminal/common/terminal.js'; +import { GeneralShellType, TerminalShellType, WindowsShellType } from '../../../../../platform/terminal/common/terminal.js'; import { TerminalSuggestSettingId } from '../common/terminalSuggestConfiguration.js'; import { TerminalCompletionItemKind, type ITerminalCompletion } from './terminalCompletionItem.js'; import { env as processEnv } from '../../../../../base/common/process.js'; import type { IProcessEnvironment } from '../../../../../base/common/platform.js'; import { timeout } from '../../../../../base/common/async.js'; +import { gitBashToWindowsPath } from './terminalGitBashHelpers.js'; export const ITerminalCompletionService = createDecorator('terminalCompletionService'); @@ -190,7 +191,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return completionItems; } if (completions.resourceRequestConfig) { - const resourceCompletions = await this.resolveResources(completions.resourceRequestConfig, promptValue, cursorPosition, provider.id, capabilities); + const resourceCompletions = await this.resolveResources(completions.resourceRequestConfig, promptValue, cursorPosition, provider.id, capabilities, shellType); if (resourceCompletions) { completionItems.push(...resourceCompletions); } @@ -202,7 +203,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return results.filter(result => !!result).flat(); } - async resolveResources(resourceRequestConfig: TerminalResourceRequestConfig, promptValue: string, cursorPosition: number, provider: string, capabilities: ITerminalCapabilityStore): Promise { + async resolveResources(resourceRequestConfig: TerminalResourceRequestConfig, promptValue: string, cursorPosition: number, provider: string, capabilities: ITerminalCapabilityStore, shellType?: TerminalShellType): Promise { const useWindowsStylePath = resourceRequestConfig.pathSeparator === '\\'; if (useWindowsStylePath) { // for tests, make sure the right path separator is used @@ -280,7 +281,11 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo break; } case 'absolute': { - lastWordFolderResource = URI.file(lastWordFolder.replaceAll('\\ ', ' ')); + if (shellType === WindowsShellType.GitBash) { + lastWordFolderResource = URI.file(gitBashToWindowsPath(lastWordFolder, this._processEnv.SystemDrive)); + } else { + lastWordFolderResource = URI.file(lastWordFolder.replaceAll('\\ ', ' ')); + } break; } case 'relative': { @@ -349,7 +354,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label, provider, kind: TerminalCompletionItemKind.Folder, - detail: getFriendlyPath(lastWordFolderResource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder), + detail: getFriendlyPath(lastWordFolderResource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -363,9 +368,17 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo for (const child of stat.children) { let kind: TerminalCompletionItemKind | undefined; if (foldersRequested && child.isDirectory) { - kind = TerminalCompletionItemKind.Folder; + if (child.isSymbolicLink) { + kind = TerminalCompletionItemKind.SymbolicLinkFolder; + } else { + kind = TerminalCompletionItemKind.Folder; + } } else if (filesRequested && child.isFile) { - kind = TerminalCompletionItemKind.File; + if (child.isSymbolicLink) { + kind = TerminalCompletionItemKind.SymbolicLinkFile; + } else { + kind = TerminalCompletionItemKind.File; + } } if (kind === undefined) { continue; @@ -394,7 +407,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label, provider, kind, - detail: getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind), + detail: getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -420,8 +433,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } const useRelative = config === 'relative'; const kind = TerminalCompletionItemKind.Folder; - const label = useRelative ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind); - const detail = useRelative ? `CDPATH ${getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind)}` : `CDPATH`; + const label = useRelative ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind, shellType); + const detail = useRelative ? `CDPATH ${getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind, shellType)}` : `CDPATH`; resourceCompletions.push({ label, provider, @@ -453,7 +466,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label, provider, kind: TerminalCompletionItemKind.Folder, - detail: getFriendlyPath(parentDir, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder), + detail: getFriendlyPath(parentDir, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -478,7 +491,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label: '~', provider, kind: TerminalCompletionItemKind.Folder, - detail: typeof homeResource === 'string' ? homeResource : getFriendlyPath(homeResource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder), + detail: typeof homeResource === 'string' ? homeResource : getFriendlyPath(homeResource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -500,14 +513,15 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } } -function getFriendlyPath(uri: URI, pathSeparator: string, kind: TerminalCompletionItemKind): string { +function getFriendlyPath(uri: URI, pathSeparator: string, kind: TerminalCompletionItemKind, shellType?: TerminalShellType): string { let path = uri.fsPath; + const sep = shellType === WindowsShellType.GitBash ? '\\' : pathSeparator; // Ensure folders end with the path separator to differentiate presentation from files - if (kind === TerminalCompletionItemKind.Folder && !path.endsWith(pathSeparator)) { - path += pathSeparator; + if (kind === TerminalCompletionItemKind.Folder && !path.endsWith(sep)) { + path += sep; } // Ensure drive is capitalized on Windows - if (pathSeparator === '\\' && path.match(/^[a-zA-Z]:\\/)) { + if (sep === '\\' && path.match(/^[a-zA-Z]:\\/)) { path = `${path[0].toUpperCase()}:${path.slice(2)}`; } return path; @@ -519,6 +533,9 @@ function getFriendlyPath(uri: URI, pathSeparator: string, kind: TerminalCompleti */ function addPathRelativePrefix(text: string, resourceRequestConfig: Pick, lastWordFolderHasDotPrefix: boolean): string { if (!lastWordFolderHasDotPrefix) { + if (text.startsWith(resourceRequestConfig.pathSeparator)) { + return `.${text}`; + } return `.${resourceRequestConfig.pathSeparator}${text}`; } return text; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalGitBashHelpers.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalGitBashHelpers.ts new file mode 100644 index 00000000000..32363fac9f9 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalGitBashHelpers.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +/** + * Converts a Git Bash absolute path to a Windows absolute path. + * Examples: + * "/" => "C:\\" + * "/c/" => "C:\\" + * "/c/Users/foo" => "C:\\Users\\foo" + * "/d/bar" => "D:\\bar" + */ +export function gitBashToWindowsPath(path: string, driveLetter?: string): string { + // Dynamically determine the system drive (default to 'C:' if not set) + const systemDrive = (driveLetter || 'C:').toUpperCase(); + // Handle root "/" + if (path === '/') { + return `${systemDrive}\\`; + } + const match = path.match(/^\/([a-zA-Z])(\/.*)?$/); + if (match) { + const drive = match[1].toUpperCase(); + const rest = match[2] ? match[2].replace(/\//g, '\\') : '\\'; + return `${drive}:${rest}`; + } + // Fallback: just replace slashes + return path.replace(/\//g, '\\'); +} + +/** + * + * @param path A Windows-style absolute path (e.g., "C:\Users\foo"). + * Converts it to a Git Bash-style absolute path (e.g., "/c/Users/foo"). + * @returns The Git Bash-style absolute path. + */ +export function windowsToGitBashPath(path: string): string { + // Convert Windows path (e.g. C:\Users\foo) to Git Bash path (e.g. /c/Users/foo) + return path + .replace(/^[a-zA-Z]:\\/, match => `/${match[0].toLowerCase()}/`) + .replace(/\\/g, '/'); +} diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 7757d450c45..846b35e51cc 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -37,7 +37,7 @@ import { TerminalCompletionItem, TerminalCompletionItemKind, type ITerminalCompl import { IntervalTimer, TimeoutTimer } from '../../../../../base/common/async.js'; import { localize } from '../../../../../nls.js'; import { TerminalSuggestTelemetry } from './terminalSuggestTelemetry.js'; -import { terminalSymbolAliasIcon, terminalSymbolArgumentIcon, terminalSymbolEnumMember, terminalSymbolFileIcon, terminalSymbolFlagIcon, terminalSymbolInlineSuggestionIcon, terminalSymbolMethodIcon, terminalSymbolOptionIcon, terminalSymbolFolderIcon } from './terminalSymbolIcons.js'; +import { terminalSymbolAliasIcon, terminalSymbolArgumentIcon, terminalSymbolEnumMember, terminalSymbolFileIcon, terminalSymbolFlagIcon, terminalSymbolInlineSuggestionIcon, terminalSymbolMethodIcon, terminalSymbolOptionIcon, terminalSymbolFolderIcon, terminalSymbolSymbolicLinkFileIcon, terminalSymbolSymbolicLinkFolderIcon } from './terminalSymbolIcons.js'; export interface ISuggestController { isPasting: boolean; @@ -93,6 +93,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _kindToIconMap = new Map([ [TerminalCompletionItemKind.File, terminalSymbolFileIcon], [TerminalCompletionItemKind.Folder, terminalSymbolFolderIcon], + [TerminalCompletionItemKind.SymbolicLinkFile, terminalSymbolSymbolicLinkFileIcon], + [TerminalCompletionItemKind.SymbolicLinkFolder, terminalSymbolSymbolicLinkFolderIcon], [TerminalCompletionItemKind.Method, terminalSymbolMethodIcon], [TerminalCompletionItemKind.Alias, terminalSymbolAliasIcon], [TerminalCompletionItemKind.Argument, terminalSymbolArgumentIcon], @@ -106,6 +108,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _kindToKindLabelMap = new Map([ [TerminalCompletionItemKind.File, localize('file', 'File')], [TerminalCompletionItemKind.Folder, localize('folder', 'Folder')], + [TerminalCompletionItemKind.SymbolicLinkFile, localize('symbolicLinkFile', 'Symbolic Link File')], + [TerminalCompletionItemKind.SymbolicLinkFolder, localize('symbolicLinkFolder', 'Symbolic Link Folder')], [TerminalCompletionItemKind.Method, localize('method', 'Method')], [TerminalCompletionItemKind.Alias, localize('alias', 'Alias')], [TerminalCompletionItemKind.Argument, localize('argument', 'Argument')], diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSymbolIcons.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSymbolIcons.ts index 5f216f62014..56d5d1559bc 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSymbolIcons.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSymbolIcons.ts @@ -20,6 +20,9 @@ export const TERMINAL_SYMBOL_ICON_INLINE_SUGGESTION_FOREGROUND = registerColor(' export const TERMINAL_SYMBOL_ICON_FILE_FOREGROUND = registerColor('terminalSymbolIcon.fileForeground', SYMBOL_ICON_FILE_FOREGROUND, localize('terminalSymbolIcon.fileForeground', 'The foreground color for a file icon. These icons will appear in the terminal suggest widget.')); export const TERMINAL_SYMBOL_ICON_FOLDER_FOREGROUND = registerColor('terminalSymbolIcon.folderForeground', SYMBOL_ICON_FOLDER_FOREGROUND, localize('terminalSymbolIcon.folderForeground', 'The foreground color for a folder icon. These icons will appear in the terminal suggest widget.')); +export const TERMINAL_SYMBOL_ICON_SYMBOLIC_LINK_FILE_FOREGROUND = registerColor('terminalSymbolIcon.symbolicLinkFileForeground', SYMBOL_ICON_FILE_FOREGROUND, localize('terminalSymbolIcon.symbolicLinkFileForeground', 'The foreground color for a symbolic link file icon. These icons will appear in the terminal suggest widget.')); +export const TERMINAL_SYMBOL_ICON_SYMBOLIC_LINK_FOLDER_FOREGROUND = registerColor('terminalSymbolIcon.symbolicLinkFolderForeground', SYMBOL_ICON_FOLDER_FOREGROUND, localize('terminalSymbolIcon.symbolicLinkFolderForeground', 'The foreground color for a symbolic link folder icon. These icons will appear in the terminal suggest widget.')); + export const terminalSymbolFlagIcon = registerIcon('terminal-symbol-flag', Codicon.flag, localize('terminalSymbolFlagIcon', 'Icon for flags in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_FLAG_FOREGROUND); export const terminalSymbolAliasIcon = registerIcon('terminal-symbol-alias', Codicon.symbolMethod, localize('terminalSymbolAliasIcon', 'Icon for aliases in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_ALIAS_FOREGROUND); export const terminalSymbolEnumMember = registerIcon('terminal-symbol-option-value', Codicon.symbolEnumMember, localize('terminalSymbolOptionValue', 'Icon for enum members in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_OPTION_VALUE_FOREGROUND); @@ -29,3 +32,6 @@ export const terminalSymbolOptionIcon = registerIcon('terminal-symbol-option', C export const terminalSymbolInlineSuggestionIcon = registerIcon('terminal-symbol-inline-suggestion', Codicon.star, localize('terminalSymbolInlineSuggestionIcon', 'Icon for inline suggestions in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_INLINE_SUGGESTION_FOREGROUND); export const terminalSymbolFileIcon = registerIcon('terminal-symbol-file', Codicon.symbolFile, localize('terminalSymbolFileIcon', 'Icon for files in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_FILE_FOREGROUND); export const terminalSymbolFolderIcon = registerIcon('terminal-symbol-folder', Codicon.symbolFolder, localize('terminalSymbolFolderIcon', 'Icon for folders in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_FOLDER_FOREGROUND); + +export const terminalSymbolSymbolicLinkFileIcon = registerIcon('terminal-symbol-symbolic-link-file', Codicon.fileSymlinkFile, localize('terminalSymbolSymbolicLinkFileIcon', 'Icon for symbolic link files in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_SYMBOLIC_LINK_FILE_FOREGROUND); +export const terminalSymbolSymbolicLinkFolderIcon = registerIcon('terminal-symbol-symbolic-link-folder', Codicon.fileSymlinkDirectory, localize('terminalSymbolSymbolicLinkFolderIcon', 'Icon for symbolic link folders in the terminal suggest widget.'), TERMINAL_SYMBOL_ICON_SYMBOLIC_LINK_FOLDER_FOREGROUND); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts index 3a46093bb72..e39bd78a386 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts @@ -7,6 +7,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/ import { TerminalCompletionModel } from '../../browser/terminalCompletionModel.js'; import { LineContext } from '../../../../../services/suggest/browser/simpleCompletionModel.js'; import { TerminalCompletionItem, TerminalCompletionItemKind, type ITerminalCompletion } from '../../browser/terminalCompletionItem.js'; +import type { CompletionItemLabel } from '../../../../../services/suggest/browser/simpleCompletionItem.js'; function createItem(options: Partial): TerminalCompletionItem { return new TerminalCompletionItem({ @@ -41,7 +42,7 @@ function createFolderItemsModel(...labels: string[]): TerminalCompletionModel { ); } -function assertItems(model: TerminalCompletionModel, labels: string[]): void { +function assertItems(model: TerminalCompletionModel, labels: (string | CompletionItemLabel)[]): void { assert.deepStrictEqual(model.items.map(i => i.completion.label), labels); assert.strictEqual(model.items.length, labels.length); // sanity check } @@ -296,4 +297,83 @@ suite('TerminalCompletionModel', function () { }); + + suite('git branch priority sorting', () => { + test('should prioritize main and master branches for git commands', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'feature-branch' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'master' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'development' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'main' }) + ]; + const model = new TerminalCompletionModel(items, new LineContext('git checkout ', 0)); + assertItems(model, ['main', 'master', 'development', 'feature-branch']); + }); + + test('should prioritize main and master branches for git switch command', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'feature-branch' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'main' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'another-feature' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'master' }) + ]; + const model = new TerminalCompletionModel(items, new LineContext('git switch ', 0)); + assertItems(model, ['main', 'master', 'another-feature', 'feature-branch']); + }); + + test('should not prioritize main and master for non-git commands', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'feature-branch' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'master' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'main' }) + ]; + const model = new TerminalCompletionModel(items, new LineContext('ls ', 0)); + assertItems(model, ['feature-branch', 'main', 'master']); + }); + + test('should handle git commands with leading whitespace', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'feature-branch' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'master' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'main' }) + ]; + const model = new TerminalCompletionModel(items, new LineContext(' git checkout ', 0)); + assertItems(model, ['main', 'master', 'feature-branch']); + }); + + test('should work with complex label objects', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: { label: 'feature-branch', description: 'Feature branch' } }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: { label: 'master', description: 'Master branch' } }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: { label: 'main', description: 'Main branch' } }) + ]; + const model = new TerminalCompletionModel(items, new LineContext('git checkout ', 0)); + assertItems(model, [ + { label: "main", description: "Main branch" }, + { label: "master", description: "Master branch" }, + { label: "feature-branch", description: "Feature branch" }, + ]); + }); + + test('should not prioritize branches with similar names', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'mainline' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'masterpiece' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'main' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'master' }) + ]; + const model = new TerminalCompletionModel(items, new LineContext('git checkout ', 0)); + assertItems(model, ['main', 'master', 'mainline', 'masterpiece']); + }); + + test('should prioritize for git branch -d', () => { + const items = [ + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'main' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'master' }), + createItem({ kind: TerminalCompletionItemKind.Argument, label: 'dev' }) + ]; + const model = new TerminalCompletionModel(items, new LineContext('git branch -d ', 0)); + assertItems(model, ['main', 'master', 'dev']); + }); + }); }); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 9a046f452a3..30ae35bb978 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -18,6 +18,8 @@ import { ShellEnvDetectionCapability } from '../../../../../../platform/terminal import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalCompletion, TerminalCompletionItemKind } from '../../browser/terminalCompletionItem.js'; import { count } from '../../../../../../base/common/strings.js'; +import { WindowsShellType } from '../../../../../../platform/terminal/common/terminal.js'; +import { gitBashToWindowsPath, windowsToGitBashPath } from '../../browser/terminalGitBashHelpers.js'; const pathSeparator = isWindows ? '\\' : '/'; @@ -35,7 +37,8 @@ interface IAssertionCommandLineConfig { /** * Assert the set of completions exist exactly, including their order. */ -function assertCompletions(actual: ITerminalCompletion[] | undefined, expected: IAssertionTerminalCompletion[], expectedConfig: IAssertionCommandLineConfig) { +function assertCompletions(actual: ITerminalCompletion[] | undefined, expected: IAssertionTerminalCompletion[], expectedConfig: IAssertionCommandLineConfig, pathSep?: string) { + const sep = pathSep ?? pathSeparator; assert.deepStrictEqual( actual?.map(e => ({ label: e.label, @@ -44,8 +47,8 @@ function assertCompletions(actual: ITerminalCompletion[] | undefined, expected: replacementIndex: e.replacementIndex, replacementLength: e.replacementLength, })), expected.map(e => ({ - label: e.label.replaceAll('/', pathSeparator), - detail: e.detail ? e.detail.replaceAll('/', pathSeparator) : '', + label: e.label.replaceAll('/', sep), + detail: e.detail ? e.detail.replaceAll('/', sep) : '', kind: e.kind ?? TerminalCompletionItemKind.Folder, replacementIndex: expectedConfig.replacementIndex, replacementLength: expectedConfig.replacementLength, @@ -608,4 +611,70 @@ suite('TerminalCompletionService', () => { ], { replacementIndex: 3, replacementLength: 0 }); }); }); + + if (isWindows) { + suite('gitbash', () => { + test('should convert Git Bash absolute path to Windows absolute path', () => { + assert.strictEqual(gitBashToWindowsPath('/'), 'C:\\'); + assert.strictEqual(gitBashToWindowsPath('/c/'), 'C:\\'); + assert.strictEqual(gitBashToWindowsPath('/c/Users/foo'), 'C:\\Users\\foo'); + assert.strictEqual(gitBashToWindowsPath('/d/bar'), 'D:\\bar'); + }); + + test('should convert Windows absolute path to Git Bash absolute path', () => { + assert.strictEqual(windowsToGitBashPath('C:\\'), '/c/'); + assert.strictEqual(windowsToGitBashPath('C:\\Users\\foo'), '/c/Users/foo'); + assert.strictEqual(windowsToGitBashPath('D:\\bar'), '/d/bar'); + assert.strictEqual(windowsToGitBashPath('E:\\some\\path'), '/e/some/path'); + }); + test('resolveResources with cwd as Windows path (relative)', async () => { + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.file('C:\\Users\\foo'), + foldersRequested: true, + filesRequested: true, + pathSeparator: '/' + }; + validResources = [ + URI.file('C:\\Users\\foo'), + URI.file('C:\\Users\\foo\\bar'), + URI.file('C:\\Users\\foo\\baz.txt') + ]; + childResources = [ + { resource: URI.file('C:\\Users\\foo\\bar'), isDirectory: true }, + { resource: URI.file('C:\\Users\\foo\\baz.txt'), isFile: true } + ]; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, capabilities, WindowsShellType.GitBash); + assertCompletions(result, [ + { label: './', detail: 'C:\\Users\\foo\\' }, + { label: './bar/', detail: 'C:\\Users\\foo\\bar\\' }, + { label: './baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File }, + { label: './../', detail: 'C:\\Users\\' } + ], { replacementIndex: 0, replacementLength: 2 }, '/'); + }); + + test('resolveResources with cwd as Windows path (absolute)', async () => { + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.file('C:\\Users\\foo'), + foldersRequested: true, + filesRequested: true, + pathSeparator: '/' + }; + validResources = [ + URI.file('C:\\Users\\foo'), + URI.file('C:\\Users\\foo\\bar'), + URI.file('C:\\Users\\foo\\baz.txt') + ]; + childResources = [ + { resource: URI.file('C:\\Users\\foo\\bar'), isDirectory: true }, + { resource: URI.file('C:\\Users\\foo\\baz.txt'), isFile: true } + ]; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/c/Users/foo/', 13, provider, capabilities, WindowsShellType.GitBash); + assertCompletions(result, [ + { label: '/c/Users/foo/', detail: 'C:\\Users\\foo\\' }, + { label: '/c/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' }, + { label: '/c/Users/foo/baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File }, + ], { replacementIndex: 0, replacementLength: 13 }, '/'); + }); + }); + } }); diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index ce9d447356f..c8796a108b2 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -10,6 +10,7 @@ import { Event } from '../../../../../base/common/event.js'; import { Iterable } from '../../../../../base/common/iterator.js'; import { Lazy } from '../../../../../base/common/lazy.js'; import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable, combinedDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { ScrollEvent } from '../../../../../base/common/scrollable.js'; import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor, IDiffEditorConstructionOptions } from '../../../../../editor/browser/editorBrowser.js'; import { CodeEditorWidget } from '../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; @@ -18,30 +19,31 @@ import { DiffEditorWidget } from '../../../../../editor/browser/widget/diffEdito import { EmbeddedDiffEditorWidget } from '../../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js'; import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IDiffEditorOptions, IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; +import { ITextModel } from '../../../../../editor/common/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { peekViewResultsBackground } from '../../../../../editor/contrib/peekView/browser/peekView.js'; import { localize } from '../../../../../nls.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { TerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; import { formatMessageForTerminal } from '../../../../../platform/terminal/common/terminalStrings.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { IEditorConfiguration } from '../../../../browser/parts/editor/textEditor.js'; import { EditorModel } from '../../../../common/editor/editorModel.js'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from '../../../../common/theme.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js'; +import { CALL_STACK_WIDGET_HEADER_HEIGHT } from '../../../debug/browser/callStackWidget.js'; import { DetachedProcessInfo } from '../../../terminal/browser/detachedTerminal.js'; import { IDetachedTerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; import { getXtermScaledDimensions } from '../../../terminal/browser/xterm/xtermTerminal.js'; import { TERMINAL_BACKGROUND_COLOR } from '../../../terminal/common/terminalColorRegistry.js'; -import { colorizeTestMessageInEditor } from '../testMessageColorizer.js'; -import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject } from './testResultsSubject.js'; import { Testing } from '../../common/constants.js'; import { MutableObservableValue } from '../../common/observableValue.js'; import { ITaskRawOutput, ITestResult, ITestRunTaskResults, LiveTestResult, TestResultItemChangeReason } from '../../common/testResult.js'; import { ITestMessage, TestMessageType, getMarkId } from '../../common/testTypes.js'; -import { ScrollEvent } from '../../../../../base/common/scrollable.js'; -import { CALL_STACK_WIDGET_HEADER_HEIGHT } from '../../../debug/browser/callStackWidget.js'; -import { ITextModel } from '../../../../../editor/common/model.js'; +import { colorizeTestMessageInEditor } from '../testMessageColorizer.js'; +import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject } from './testResultsSubject.js'; class SimpleDiffEditorModel extends EditorModel { @@ -110,6 +112,35 @@ const diffEditorOptions: IDiffEditorConstructionOptions = { diffAlgorithm: 'advanced', }; +function applyEditorMirrorOptions(base: T, cfg: IConfigurationService, update: (options: Partial) => void) { + const immutable = new Set(Object.keys(base)); + function applyCurrent() { + const configuration = cfg.getValue('editor'); + + let changed = false; + const patch: Partial = {}; + for (const [key, value] of Object.entries(configuration)) { + if (!immutable.has(key) && (base as any)[key] !== value) { + (patch as any)[key] = value; + changed = true; + } + } + + return changed ? patch : undefined; + } + + Object.assign(base, applyCurrent()); + + return cfg.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor')) { + const patch = applyCurrent(); + if (patch) { + update(patch); + Object.assign(base, patch); + } + } + }); +} export class DiffContentProvider extends Disposable implements IPeekOutputRenderer { private readonly widget = this._register(new MutableDisposable()); @@ -126,6 +157,7 @@ export class DiffContentProvider extends Disposable implements IPeekOutputRender private readonly container: HTMLElement, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextModelService private readonly modelService: ITextModelService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); } @@ -148,21 +180,32 @@ export class DiffContentProvider extends Disposable implements IPeekOutputRender const model = this.model.value = new SimpleDiffEditorModel(original, modified); if (!this.widget.value) { - this.widget.value = this.editor ? this.instantiationService.createInstance( + const options = { ...diffEditorOptions }; + const listener = applyEditorMirrorOptions( + options, + this.configurationService, + u => editor.updateOptions(u) + ); + + const editor = this.widget.value = this.editor ? this.instantiationService.createInstance( EmbeddedDiffEditorWidget, this.container, - diffEditorOptions, + options, {}, this.editor, ) : this.instantiationService.createInstance( DiffEditorWidget, this.container, - diffEditorOptions, + options, {}, ); + Event.once(editor.onDidDispose)(() => { + listener.dispose(); + }); + if (this.dimension) { - this.widget.value.layout(this.dimension); + editor.layout(this.dimension); } } @@ -191,6 +234,7 @@ export class DiffContentProvider extends Disposable implements IPeekOutputRender editor.getOriginalEditor().getContentHeight(), editor.getModifiedEditor().getContentHeight() ); + editor.updateOptions({ scrollbar: { ...commonEditorOptions.scrollbar, handleMouseWheel: !hasMultipleFrames } }); this.helper = new ScrollHelper(hasMultipleFrames, height, dimensions.height); return height; } @@ -293,6 +337,7 @@ export class PlainTextMessagePeek extends Disposable implements IPeekOutputRende private readonly container: HTMLElement, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextModelService private readonly modelService: ITextModelService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); } @@ -311,21 +356,32 @@ export class PlainTextMessagePeek extends Disposable implements IPeekOutputRende const modelRef = this.model.value = await this.modelService.createModelReference(subject.messageUri); if (!this.widget.value) { - this.widget.value = this.editor ? this.instantiationService.createInstance( + const options = { ...commonEditorOptions }; + const listener = applyEditorMirrorOptions( + options, + this.configurationService, + u => editor.updateOptions(u) + ); + + const editor = this.widget.value = this.editor ? this.instantiationService.createInstance( EmbeddedCodeEditorWidget, this.container, - commonEditorOptions, + options, {}, this.editor, ) : this.instantiationService.createInstance( CodeEditorWidget, this.container, - commonEditorOptions, + options, { isSimpleWidget: true } ); + Event.once(editor.onDidDispose)(() => { + listener.dispose(); + }); + if (this.dimension) { - this.widget.value.layout(this.dimension); + editor.layout(this.dimension); } } @@ -355,6 +411,8 @@ export class PlainTextMessagePeek extends Disposable implements IPeekOutputRende editor.layout(dimensions); const height = editor.getContentHeight(); this.helper = new ScrollHelper(hasMultipleFrames, height, dimensions.height); + editor.updateOptions({ scrollbar: { ...commonEditorOptions.scrollbar, handleMouseWheel: !hasMultipleFrames } }); + return height; } } diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 1895434ddba..93d3a8d56cf 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -745,6 +745,9 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer { - if (this.dimension && !this.isDoingLayoutUpdate) { + const width = this.splitView.getViewSize(SubView.Diff); + if (this.dimension && !this.isDoingLayoutUpdate && width !== -1) { this.isDoingLayoutUpdate = true; - topFrame.height.set(provider.layout(this.dimension, hasMultipleFrames)!, undefined); + topFrame.height.set(provider.layout({ width, height: this.dimension.height }, hasMultipleFrames)!, undefined); this.isDoingLayoutUpdate = false; } })); diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index ac47408247d..3c3a2f50f35 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -1367,11 +1367,17 @@ class TestErrorContentWidget extends Disposable implements IContentWidget { super(); const setMarginTop = () => { - const lineHeight = editor.getOption(EditorOption.lineHeight); + const lineHeight = editor.getLineHeightForPosition(position); this.node.root.style.marginTop = (lineHeight - ERROR_CONTENT_WIDGET_HEIGHT) / 2 + 'px'; }; setMarginTop(); + this._register(editor.onDidChangeLineHeight(e => { + if (e.affects(position)) { + setMarginTop(); + } + })); + this._register(editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.lineHeight)) { setMarginTop(); @@ -1387,10 +1393,10 @@ class TestErrorContentWidget extends Disposable implements IContentWidget { text = lf === -1 ? msg : msg.slice(0, lf); } - this.node.root.addEventListener('click', e => { + this._register(dom.addDisposableListener(this.node.root, dom.EventType.CLICK, e => { this.peekOpener.peekUri(uri); e.preventDefault(); - }); + })); const ctrl = TestingOutputPeekController.get(editor); if (ctrl) { diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts index d508a0d8785..393c8c36a12 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts @@ -12,7 +12,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IWorkbenchContribution } from '../../../common/contributions.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; import { readTrustedDomains, TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, TRUSTED_DOMAINS_STORAGE_KEY } from './trustedDomains.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const TRUSTED_DOMAINS_SCHEMA = 'trustedDomains'; @@ -109,7 +109,7 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith trustedDomainsContent.indexOf(CONFIG_HELP_TEXT_PRE) === -1 || trustedDomainsContent.indexOf(CONFIG_HELP_TEXT_AFTER) === -1 || trustedDomainsContent.indexOf(configuring ?? '') === -1 || - [...defaultTrustedDomains, ...trustedDomains].some(d => !assertIsDefined(trustedDomainsContent).includes(d)) + [...defaultTrustedDomains, ...trustedDomains].some(d => !assertReturnsDefined(trustedDomainsContent).includes(d)) ) { trustedDomainsContent = computeTrustedDomainContent(defaultTrustedDomains, trustedDomains, configuring); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 21d1c9b2065..8fbd8a59075 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -53,8 +53,6 @@ import { IWorkbenchIssueService } from '../../issue/common/issue.js'; import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; import { isWeb } from '../../../../base/common/platform.js'; -import { PromptsConfig } from '../../../../platform/prompts/common/config.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; type ConfigureSyncQuickPickItem = { id: SyncResource; label: string; description?: string }; @@ -116,8 +114,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IHostService private readonly hostService: IHostService, @ICommandService private readonly commandService: ICommandService, - @IWorkbenchIssueService private readonly workbenchIssueService: IWorkbenchIssueService, - @IConfigurationService private readonly configService: IConfigurationService, + @IWorkbenchIssueService private readonly workbenchIssueService: IWorkbenchIssueService ) { super(); @@ -615,16 +612,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }, { id: SyncResource.Profiles, label: getSyncAreaLabel(SyncResource.Profiles), + }, { + id: SyncResource.Prompts, + label: getSyncAreaLabel(SyncResource.Prompts) }]; - // if the `reusable prompt` feature is enabled and in vscode - // insiders, add the `Prompts` resource item to the list - if (PromptsConfig.enabled(this.configService) === true) { - result.push({ - id: SyncResource.Prompts, - label: getSyncAreaLabel(SyncResource.Prompts) - }); - } return result; } diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts similarity index 99% rename from src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts rename to src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 6f3cd48be11..2b9a039ff3b 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -5,7 +5,7 @@ import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { IUserDataSyncUtilService, SyncStatus } from '../../../../platform/userDataSync/common/userDataSync.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js'; import { localize, localize2 } from '../../../../nls.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/webview/electron-sandbox/webview.contribution.ts rename to src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts similarity index 100% rename from src/vs/workbench/contrib/webview/electron-sandbox/webviewCommands.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts similarity index 100% rename from src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts similarity index 100% rename from src/vs/workbench/contrib/webview/electron-sandbox/webviewService.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewService.ts diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-browser/windowIgnoreMenuShortcutsManager.ts similarity index 100% rename from src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts rename to src/vs/workbench/contrib/webview/electron-browser/windowIgnoreMenuShortcutsManager.ts diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 68f17c615b3..5ec6e750839 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -30,8 +30,6 @@ import { IActivityService, NumberBadge } from '../../../services/activity/common import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; -declare const ResizeObserver: any; - const storageKeys = { webviewState: 'webviewState', } as const; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 8d728275a51..70d8b2ac1f7 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -23,7 +23,7 @@ import { parse } from '../../../../base/common/marshalling.js'; import { Schemas, matchesScheme } from '../../../../base/common/network.js'; import { OS } from '../../../../base/common/platform.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import './media/gettingStarted.css'; @@ -158,6 +158,7 @@ export class GettingStartedPage extends EditorPane { private categoriesSlide!: HTMLElement; private stepsContent!: HTMLElement; private stepMediaComponent!: HTMLElement; + private nextButton!: HTMLButtonElement; private webview!: IWebviewElement; private layoutMarkdown: (() => void) | undefined; @@ -276,7 +277,7 @@ export class GettingStartedPage extends EditorPane { ourStep.done = step.done; if (category.id === this.currentWalkthrough?.id) { - const badgeelements = assertIsDefined(this.window.document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); + const badgeelements = assertReturnsDefined(this.window.document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); badgeelements.forEach(badgeelement => { if (step.done) { badgeelement.setAttribute('aria-checked', 'true'); @@ -490,7 +491,7 @@ export class GettingStartedPage extends EditorPane { } private toggleStepCompletion(argument: string) { - const stepToggle = assertIsDefined(this.currentWalkthrough?.steps.find(step => step.id === argument)); + const stepToggle = assertReturnsDefined(this.currentWalkthrough?.steps.find(step => step.id === argument)); if (stepToggle.done) { this.gettingStartedService.deprogressStep(argument); } else { @@ -530,7 +531,7 @@ export class GettingStartedPage extends EditorPane { if (!this.currentWalkthrough) { throw Error('no walkthrough selected'); } - const stepToExpand = assertIsDefined(this.currentWalkthrough.steps.find(step => step.id === stepId)); + const stepToExpand = assertReturnsDefined(this.currentWalkthrough.steps.find(step => step.id === stepId)); if (!forceRebuild && this.currentMediaComponent === stepId) { return; } this.currentMediaComponent = stepId; @@ -774,7 +775,7 @@ export class GettingStartedPage extends EditorPane { // No steps around... just ignore. return; } - id = assertIsDefined(stepElement.getAttribute('data-step-id')); + id = assertReturnsDefined(stepElement.getAttribute('data-step-id')); } stepElement.parentElement?.querySelectorAll('.expanded').forEach(node => { if (node.getAttribute('data-step-id') !== id) { @@ -1215,7 +1216,7 @@ export class GettingStartedPage extends EditorPane { const stats = this.getWalkthroughCompletionStats(category); - const bar = assertIsDefined(element.querySelector('.progress-bar-inner')) as HTMLDivElement; + const bar = assertReturnsDefined(element.querySelector('.progress-bar-inner')) as HTMLDivElement; bar.setAttribute('aria-valuemin', '0'); bar.setAttribute('aria-valuenow', '' + stats.stepsComplete); bar.setAttribute('aria-valuemax', '' + stats.stepsTotal); @@ -1272,7 +1273,7 @@ export class GettingStartedPage extends EditorPane { const gettingStartedSize = Math.floor(fullSize.width / 2); const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); - this.groupsService.setSize(assertIsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); + this.groupsService.setSize(assertReturnsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); } const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); @@ -1504,6 +1505,20 @@ export class GettingStartedPage extends EditorPane { prevButton.removeAttribute('tabindex'); } } + + // Update next button text for final slide + if (this.nextButton) { + const isLastSlide = newIndex === steps.length - 1; + const textNode = this.nextButton.firstChild as Text; + if (textNode && textNode.nodeType === Node.TEXT_NODE) { + textNode.textContent = isLastSlide + ? localize('last', "Start coding") + : localize('next', "Next"); + } + this.nextButton.setAttribute('aria-label', isLastSlide + ? localize('lastStep', "Start coding") + : localize('nextStep', "Next")); + } } private buildNewCategorySlide(categoryID: string, selectedStep?: string) { @@ -1653,11 +1668,11 @@ export class GettingStartedPage extends EditorPane { }); // Add next button - const nextButton = $('button.button-link.navigation.next', { + this.nextButton = $('button.button-link.navigation.next', { 'aria-label': localize('nextStep', "Next"), }, localize('next', "Next"), $('span.codicon.codicon-arrow-right')); - navigationContainer.appendChild(nextButton); + navigationContainer.appendChild(this.nextButton); this.detailsPageDisposables.add(addDisposableListener(prevButton, 'click', () => { const currentIndex = this.getCurrentSlideIndex(allSlides); if (currentIndex > 0) { @@ -1665,7 +1680,7 @@ export class GettingStartedPage extends EditorPane { } })); - this.detailsPageDisposables.add(addDisposableListener(nextButton, 'click', () => { + this.detailsPageDisposables.add(addDisposableListener(this.nextButton, 'click', () => { const currentIndex = this.getCurrentSlideIndex(allSlides); if (currentIndex < allSlides.length - 1) { this.selectStepByIndex(currentIndex + 1, allSlides.map(s => s.steps[0]), 1); @@ -2079,7 +2094,7 @@ export class GettingStartedPage extends EditorPane { } private setSlide(toEnable: 'details' | 'categories', firstLaunch: boolean = false) { - const slideManager = assertIsDefined(this.container.querySelector('.gettingStarted')); + const slideManager = assertReturnsDefined(this.container.querySelector('.gettingStarted')); if (toEnable === 'categories') { slideManager.classList.remove('showDetails'); slideManager.classList.add('showCategories'); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts index 1ca5db0b86b..c4e3637ca2c 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts @@ -10,7 +10,7 @@ import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from '../../markdown/ import { URI } from '../../../../base/common/uri.js'; import { language } from '../../../../base/common/platform.js'; import { joinPath } from '../../../../base/common/resources.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { asWebviewUri } from '../../webview/common/webview.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { IFileService } from '../../../../platform/files/common/files.js'; @@ -230,7 +230,7 @@ export class GettingStartedDetailsRenderer { const contents = await this.readContentsOfPath(path, false); this.svgCache.set(path, contents); } - return assertIsDefined(this.svgCache.get(path)); + return assertReturnsDefined(this.svgCache.get(path)); } private async readAndCacheStepMarkdown(path: URI, base: URI): Promise { @@ -239,7 +239,7 @@ export class GettingStartedDetailsRenderer { const markdownContents = await renderMarkdownDocument(transformUris(contents, base), this.extensionService, this.languageService, { allowUnknownProtocols: true }); this.mdCache.set(path, markdownContents); } - return assertIsDefined(this.mdCache.get(path)); + return assertReturnsDefined(this.mdCache.get(path)); } private async readContentsOfPath(path: URI, useModuleId = true): Promise { diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 15600ccb568..fad3c94a4c5 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -730,16 +730,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ type: 'svg', altText: 'Language extensions', path: 'languages.svg' }, }, - { - id: 'newSettingsAndSync', - title: localize('newgettingStarted.settings.title', "Customize every aspect of VS Code"), - description: localize('newgettingStarted.settingsAndSync.description.interpolated', "[Back up and sync](command:workbench.userDataSync.actions.turnOn) settings across all your devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), - when: 'syncStatus != uninitialized', - completionEvents: ['onEvent:sync-enabled'], - media: { - type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' - }, - }, + ] } } diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index 4b6a9aeab73..b443285e3c2 100644 --- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -6,7 +6,7 @@ import { promiseWithResolvers } from '../../../../base/common/async.js'; import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { localize, localize2 } from '../../../../nls.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; import { Action2, IMenuService, MenuId, registerAction2, IMenu, MenuRegistry, MenuItemAction } from '../../../../platform/actions/common/actions.js'; @@ -43,7 +43,7 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - return assertIsDefined(NewFileTemplatesManager.Instance).run(); + return assertReturnsDefined(NewFileTemplatesManager.Instance).run(); } }); diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts index b7ed7fdb242..84889e04ebe 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts @@ -13,7 +13,7 @@ import * as marked from '../../../../base/common/marked/marked.js'; import { Schemas } from '../../../../base/common/network.js'; import { Range } from '../../../../editor/common/core/range.js'; import { createTextBufferFactory } from '../../../../editor/common/model/textModel.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; interface IWalkThroughContentProvider { @@ -100,6 +100,6 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi const markdown = textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); marked.marked(markdown, { renderer }); } - return assertIsDefined(codeEditorModel); + return assertReturnsDefined(codeEditorModel); } } diff --git a/src/vs/workbench/electron-sandbox/actions/developerActions.ts b/src/vs/workbench/electron-browser/actions/developerActions.ts similarity index 99% rename from src/vs/workbench/electron-sandbox/actions/developerActions.ts rename to src/vs/workbench/electron-browser/actions/developerActions.ts index 1bc0f68c98a..0b64a58ee06 100644 --- a/src/vs/workbench/electron-sandbox/actions/developerActions.ts +++ b/src/vs/workbench/electron-browser/actions/developerActions.ts @@ -13,7 +13,7 @@ import { IWorkbenchEnvironmentService } from '../../services/environment/common/ import { KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js'; import { IsDevelopmentContext } from '../../../platform/contextkey/common/contextkeys.js'; import { KeyCode, KeyMod } from '../../../base/common/keyCodes.js'; -import { INativeWorkbenchEnvironmentService } from '../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../services/environment/electron-browser/environmentService.js'; import { URI } from '../../../base/common/uri.js'; import { getActiveWindow } from '../../../base/browser/dom.js'; import { IDialogService } from '../../../platform/dialogs/common/dialogs.js'; diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-browser/actions/installActions.ts similarity index 100% rename from src/vs/workbench/electron-sandbox/actions/installActions.ts rename to src/vs/workbench/electron-browser/actions/installActions.ts diff --git a/src/vs/workbench/electron-sandbox/actions/media/actions.css b/src/vs/workbench/electron-browser/actions/media/actions.css similarity index 100% rename from src/vs/workbench/electron-sandbox/actions/media/actions.css rename to src/vs/workbench/electron-browser/actions/media/actions.css diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts similarity index 99% rename from src/vs/workbench/electron-sandbox/actions/windowActions.ts rename to src/vs/workbench/electron-browser/actions/windowActions.ts index 4e31e25b81f..b542b8d7344 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -6,7 +6,7 @@ import './media/actions.css'; import { URI } from '../../../base/common/uri.js'; import { localize, localize2 } from '../../../nls.js'; -import { ApplyZoomTarget, MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, applyZoom } from '../../../platform/window/electron-sandbox/window.js'; +import { ApplyZoomTarget, MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, applyZoom } from '../../../platform/window/electron-browser/window.js'; import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js'; import { getZoomLevel } from '../../../base/browser/browser.js'; import { FileKind } from '../../../platform/files/common/files.js'; diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts similarity index 99% rename from src/vs/workbench/electron-sandbox/desktop.contribution.ts rename to src/vs/workbench/electron-browser/desktop.contribution.ts index 008bc5ef3e1..f9d0fb87eed 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -27,7 +27,7 @@ import { ShutdownReason } from '../services/lifecycle/common/lifecycle.js'; import { NativeWindow } from './window.js'; import { ModifierKeyEmitter } from '../../base/browser/dom.js'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from '../common/configuration.js'; -import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../platform/window/electron-sandbox/window.js'; +import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../platform/window/electron-browser/window.js'; import { DefaultAccountManagementContribution } from '../services/accounts/common/defaultAccount.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../common/contributions.js'; diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts similarity index 96% rename from src/vs/workbench/electron-sandbox/desktop.main.ts rename to src/vs/workbench/electron-browser/desktop.main.ts index 306d1c7c685..0487ad5e2b8 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -13,20 +13,20 @@ import { domContentLoaded } from '../../base/browser/dom.js'; import { onUnexpectedError } from '../../base/common/errors.js'; import { URI } from '../../base/common/uri.js'; import { WorkspaceService } from '../services/configuration/browser/configurationService.js'; -import { INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from '../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from '../services/environment/electron-browser/environmentService.js'; import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js'; import { ILoggerService, ILogService, LogLevel } from '../../platform/log/common/log.js'; -import { NativeWorkbenchStorageService } from '../services/storage/electron-sandbox/storageService.js'; +import { NativeWorkbenchStorageService } from '../services/storage/electron-browser/storageService.js'; import { IWorkspaceContextService, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IAnyWorkspaceIdentifier, reviveIdentifier, toWorkspaceIdentifier } from '../../platform/workspace/common/workspace.js'; import { IWorkbenchConfigurationService } from '../services/configuration/common/configuration.js'; import { IStorageService } from '../../platform/storage/common/storage.js'; import { Disposable } from '../../base/common/lifecycle.js'; -import { ISharedProcessService } from '../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../platform/ipc/electron-browser/services.js'; import { IMainProcessService } from '../../platform/ipc/common/mainProcessService.js'; -import { SharedProcessService } from '../services/sharedProcess/electron-sandbox/sharedProcessService.js'; -import { RemoteAuthorityResolverService } from '../../platform/remote/electron-sandbox/remoteAuthorityResolverService.js'; +import { SharedProcessService } from '../services/sharedProcess/electron-browser/sharedProcessService.js'; +import { RemoteAuthorityResolverService } from '../../platform/remote/electron-browser/remoteAuthorityResolverService.js'; import { IRemoteAuthorityResolverService, RemoteConnectionType } from '../../platform/remote/common/remoteAuthorityResolver.js'; -import { RemoteAgentService } from '../services/remote/electron-sandbox/remoteAgentService.js'; +import { RemoteAgentService } from '../services/remote/electron-browser/remoteAgentService.js'; import { IRemoteAgentService } from '../services/remote/common/remoteAgentService.js'; import { FileService } from '../../platform/files/common/fileService.js'; import { IFileService } from '../../platform/files/common/files.js'; @@ -36,18 +36,18 @@ import { ISignService } from '../../platform/sign/common/sign.js'; import { IProductService } from '../../platform/product/common/productService.js'; import { IUriIdentityService } from '../../platform/uriIdentity/common/uriIdentity.js'; import { UriIdentityService } from '../../platform/uriIdentity/common/uriIdentityService.js'; -import { INativeKeyboardLayoutService, NativeKeyboardLayoutService } from '../services/keybinding/electron-sandbox/nativeKeyboardLayoutService.js'; -import { ElectronIPCMainProcessService } from '../../platform/ipc/electron-sandbox/mainProcessService.js'; +import { INativeKeyboardLayoutService, NativeKeyboardLayoutService } from '../services/keybinding/electron-browser/nativeKeyboardLayoutService.js'; +import { ElectronIPCMainProcessService } from '../../platform/ipc/electron-browser/mainProcessService.js'; import { LoggerChannelClient } from '../../platform/log/common/logIpc.js'; import { ProxyChannel } from '../../base/parts/ipc/common/ipc.js'; -import { NativeLogService } from '../services/log/electron-sandbox/logService.js'; +import { NativeLogService } from '../services/log/electron-browser/logService.js'; import { WorkspaceTrustEnablementService, WorkspaceTrustManagementService } from '../services/workspaces/common/workspaceTrust.js'; import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from '../../platform/workspace/common/workspaceTrust.js'; import { safeStringify } from '../../base/common/objects.js'; -import { IUtilityProcessWorkerWorkbenchService, UtilityProcessWorkerWorkbenchService } from '../services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.js'; +import { IUtilityProcessWorkerWorkbenchService, UtilityProcessWorkerWorkbenchService } from '../services/utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.js'; import { isBigSurOrNewer, isCI, isMacintosh } from '../../base/common/platform.js'; import { Schemas } from '../../base/common/network.js'; -import { DiskFileSystemProvider } from '../services/files/electron-sandbox/diskFileSystemProvider.js'; +import { DiskFileSystemProvider } from '../services/files/electron-browser/diskFileSystemProvider.js'; import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; import { IUserDataProfilesService, reviveProfile } from '../../platform/userDataProfile/common/userDataProfile.js'; import { UserDataProfilesService } from '../../platform/userDataProfile/common/userDataProfileIpc.js'; @@ -57,9 +57,9 @@ import { UserDataProfileService } from '../services/userDataProfile/common/userD import { IUserDataProfileService } from '../services/userDataProfile/common/userDataProfile.js'; import { BrowserSocketFactory } from '../../platform/remote/browser/browserSocketFactory.js'; import { RemoteSocketFactoryService, IRemoteSocketFactoryService } from '../../platform/remote/common/remoteSocketFactoryService.js'; -import { ElectronRemoteResourceLoader } from '../../platform/remote/electron-sandbox/electronRemoteResourceLoader.js'; +import { ElectronRemoteResourceLoader } from '../../platform/remote/electron-browser/electronRemoteResourceLoader.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; -import { applyZoom } from '../../platform/window/electron-sandbox/window.js'; +import { applyZoom } from '../../platform/window/electron-browser/window.js'; import { mainWindow } from '../../base/browser/window.js'; import { DefaultAccountService, IDefaultAccountService } from '../services/accounts/common/defaultAccount.js'; import { AccountPolicyService } from '../services/policies/common/accountPolicyService.js'; diff --git a/src/vs/workbench/electron-sandbox/media/window.css b/src/vs/workbench/electron-browser/media/window.css similarity index 100% rename from src/vs/workbench/electron-sandbox/media/window.css rename to src/vs/workbench/electron-browser/media/window.css diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-browser/parts/dialogs/dialog.contribution.ts similarity index 100% rename from src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts rename to src/vs/workbench/electron-browser/parts/dialogs/dialog.contribution.ts diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts b/src/vs/workbench/electron-browser/parts/dialogs/dialogHandler.ts similarity index 99% rename from src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts rename to src/vs/workbench/electron-browser/parts/dialogs/dialogHandler.ts index 6c61a590324..0806103b36a 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/electron-browser/parts/dialogs/dialogHandler.ts @@ -11,7 +11,7 @@ import { AbstractDialogHandler, IConfirmation, IConfirmationResult, IPrompt, IAs import { ILogService } from '../../../../platform/log/common/log.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { process } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { process } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { getActiveWindow } from '../../../../base/browser/dom.js'; export class NativeDialogHandler extends AbstractDialogHandler { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts b/src/vs/workbench/electron-browser/parts/titlebar/menubarControl.ts similarity index 98% rename from src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts rename to src/vs/workbench/electron-browser/parts/titlebar/menubarControl.ts index 6bbdb1b7a31..716f2280413 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/electron-browser/parts/titlebar/menubarControl.ts @@ -10,7 +10,7 @@ import { IWorkspacesService } from '../../../../platform/workspaces/common/works import { isMacintosh } from '../../../../base/common/platform.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; @@ -18,7 +18,7 @@ import { IUpdateService } from '../../../../platform/update/common/update.js'; import { IOpenRecentAction, MenubarControl } from '../../../browser/parts/titlebar/menubarControl.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from '../../../../platform/menubar/common/menubar.js'; -import { IMenubarService } from '../../../../platform/menubar/electron-sandbox/menubar.js'; +import { IMenubarService } from '../../../../platform/menubar/electron-browser/menubar.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts similarity index 99% rename from src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts rename to src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts index b437af42424..cfc3572068f 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts @@ -9,7 +9,7 @@ import { $, addDisposableListener, append, EventType, getWindow, getWindowId, hi import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { isMacintosh, isWindows, isLinux, isBigSurOrNewer } from '../../../../base/common/platform.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-browser/window.ts similarity index 99% rename from src/vs/workbench/electron-sandbox/window.ts rename to src/vs/workbench/electron-browser/window.ts index 30805019b89..d4072f5d95c 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -16,11 +16,11 @@ import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js' import { WindowMinimumSize, IOpenFileRequest, IAddRemoveFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, hasNativeTitlebar } from '../../platform/window/common/window.js'; import { ITitleService } from '../services/title/browser/titleService.js'; import { IWorkbenchThemeService } from '../services/themes/common/workbenchThemeService.js'; -import { ApplyZoomTarget, applyZoom } from '../../platform/window/electron-sandbox/window.js'; +import { ApplyZoomTarget, applyZoom } from '../../platform/window/electron-browser/window.js'; import { setFullscreen, getZoomLevel, onDidChangeZoomLevel, getZoomFactor } from '../../base/browser/browser.js'; import { ICommandService, CommandsRegistry } from '../../platform/commands/common/commands.js'; import { IResourceEditorInput } from '../../platform/editor/common/editor.js'; -import { ipcRenderer, process } from '../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ipcRenderer, process } from '../../base/parts/sandbox/electron-browser/globals.js'; import { IWorkspaceEditingService } from '../services/workspaces/common/workspaceEditing.js'; import { IMenuService, MenuId, IMenu, MenuItemAction, MenuRegistry } from '../../platform/actions/common/actions.js'; import { ICommandAction } from '../../platform/action/common/action.js'; @@ -34,7 +34,7 @@ import { isWindows, isMacintosh } from '../../base/common/platform.js'; import { IProductService } from '../../platform/product/common/productService.js'; import { INotificationService, NotificationPriority, Severity } from '../../platform/notification/common/notification.js'; import { IKeybindingService } from '../../platform/keybinding/common/keybinding.js'; -import { INativeWorkbenchEnvironmentService } from '../services/environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../services/environment/electron-browser/environmentService.js'; import { IAccessibilityService, AccessibilitySupport } from '../../platform/accessibility/common/accessibility.js'; import { WorkbenchState, IWorkspaceContextService } from '../../platform/workspace/common/workspace.js'; import { coalesce } from '../../base/common/arrays.js'; @@ -54,11 +54,11 @@ import { IRemoteAuthorityResolverService } from '../../platform/remote/common/re import { IAddressProvider, IAddress } from '../../platform/remote/common/remoteAgentConnection.js'; import { IEditorGroupsService, IEditorPart } from '../services/editor/common/editorGroupsService.js'; import { IDialogService } from '../../platform/dialogs/common/dialogs.js'; -import { AuthInfo } from '../../base/parts/sandbox/electron-sandbox/electronTypes.js'; +import { AuthInfo } from '../../base/parts/sandbox/electron-browser/electronTypes.js'; import { ILogService } from '../../platform/log/common/log.js'; import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js'; import { whenEditorClosed } from '../browser/editor.js'; -import { ISharedProcessService } from '../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../platform/ipc/electron-browser/services.js'; import { IProgressService, ProgressLocation } from '../../platform/progress/common/progress.js'; import { toErrorMessage } from '../../base/common/errorMessage.js'; import { ILabelService } from '../../platform/label/common/label.js'; @@ -67,7 +67,7 @@ import { IBannerService } from '../services/banner/browser/bannerService.js'; import { Codicon } from '../../base/common/codicons.js'; import { IUriIdentityService } from '../../platform/uriIdentity/common/uriIdentity.js'; import { IPreferencesService } from '../services/preferences/common/preferences.js'; -import { IUtilityProcessWorkerWorkbenchService } from '../services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.js'; +import { IUtilityProcessWorkerWorkbenchService } from '../services/utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.js'; import { registerWindowDriver } from '../services/driver/browser/driver.js'; import { mainWindow } from '../../base/browser/window.js'; import { BaseWindow } from '../browser/window.js'; diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts similarity index 99% rename from src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts rename to src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts index 89f1c861cff..f3aab796cc1 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts @@ -5,7 +5,7 @@ import { IAccessibilityService, AccessibilitySupport } from '../../../../platform/accessibility/common/accessibility.js'; import { isWindows, isLinux } from '../../../../base/common/platform.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { AccessibilityService } from '../../../../platform/accessibility/browser/accessibilityService.js'; diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 0b925420ce3..26fb7ff3889 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -450,6 +450,27 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.chatModelPicker', "The chat model picker dropdown menu"), supportsSubmenus: false, proposed: 'chatParticipantPrivate' + }, + { + key: 'explorer/context/chat', + id: MenuId.ChatExplorerMenu, + description: localize('menus.chatExplorer', "The Chat submenu in the explorer context menu."), + supportsSubmenus: false, + proposed: 'chatParticipantPrivate' + }, + { + key: 'editor/context/chat', + id: MenuId.ChatTextEditorMenu, + description: localize('menus.chatTextEditor', "The Chat submenu in the text editor context menu."), + supportsSubmenus: false, + proposed: 'chatParticipantPrivate' + }, + { + key: 'terminal/context/chat', + id: MenuId.ChatTerminalMenu, + description: localize('menus.chatTerminal', "The Chat submenu in the terminal context menu."), + supportsSubmenus: false, + proposed: 'chatParticipantPrivate' } ]; diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 731d8da9cb8..719ddc454c9 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -21,6 +21,7 @@ import { ExtensionsRegistry } from '../../extensions/common/extensionsRegistry.j import { match } from '../../../../base/common/glob.js'; import { URI } from '../../../../base/common/uri.js'; import { IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata } from '../../../../base/common/oauth.js'; +import { raceTimeout } from '../../../../base/common/async.js'; export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; } @@ -108,6 +109,8 @@ export class AuthenticationService extends Disposable implements IAuthentication private readonly _delegates: IAuthenticationProviderHostDelegate[] = []; + private _isDisposable: boolean = false; + constructor( @IExtensionService private readonly _extensionService: IExtensionService, @IAuthenticationAccessService authenticationAccessService: IAuthenticationAccessService, @@ -115,7 +118,7 @@ export class AuthenticationService extends Disposable implements IAuthentication @ILogService private readonly _logService: ILogService ) { super(); - + this._register(toDisposable(() => this._isDisposable = true)); this._register(authenticationAccessService.onDidChangeExtensionSessionAccess(e => { // The access has changed, not the actual session itself but extensions depend on this event firing // when they have gained access to an account so this fires that event. @@ -264,6 +267,10 @@ export class AuthenticationService extends Disposable implements IAuthentication } async getSessions(id: string, scopes?: string[], options?: IAuthenticationGetSessionsOptions, activateImmediate: boolean = false): Promise> { + if (this._isDisposable) { + return []; + } + const authProvider = this._authenticationProviders.get(id) || await this.tryActivateProvider(id, activateImmediate); if (authProvider) { // Check if the authorization server is in the list of supported authorization servers @@ -281,6 +288,10 @@ export class AuthenticationService extends Disposable implements IAuthentication } async createSession(id: string, scopes: string[], options?: IAuthenticationCreateSessionOptions): Promise { + if (this._isDisposable) { + throw new Error('Authentication service is disposed.'); + } + const authProvider = this._authenticationProviders.get(id) || await this.tryActivateProvider(id, !!options?.activateImmediate); if (authProvider) { return await authProvider.createSession(scopes, { @@ -293,6 +304,10 @@ export class AuthenticationService extends Disposable implements IAuthentication } async removeSession(id: string, sessionId: string): Promise { + if (this._isDisposable) { + throw new Error('Authentication service is disposed.'); + } + const authProvider = this._authenticationProviders.get(id); if (authProvider) { return authProvider.removeSession(sessionId); @@ -361,32 +376,30 @@ export class AuthenticationService extends Disposable implements IAuthentication return provider; } - const store = new DisposableStore(); - - // When activate has completed, the extension has made the call to `registerAuthenticationProvider`. - // However, activate cannot block on this, so the renderer may not have gotten the event yet. - const didRegister: Promise = new Promise((resolve, _) => { - store.add(Event.once(this.onDidRegisterAuthenticationProvider)(e => { - if (e.id === providerId) { - provider = this._authenticationProviders.get(providerId); - if (provider) { - resolve(provider); - } else { - throw new Error(`No authentication provider '${providerId}' is currently registered.`); - } - } - })); - }); - - const didTimeout: Promise = new Promise((_, reject) => { - const handle = setTimeout(() => { - reject('Timed out waiting for authentication provider to register'); - }, 5000); - - store.add(toDisposable(() => clearTimeout(handle))); - }); - - return Promise.race([didRegister, didTimeout]).finally(() => store.dispose()); + const store = this._register(new DisposableStore()); + try { + const result = await raceTimeout( + Event.toPromise( + Event.filter( + this.onDidRegisterAuthenticationProvider, + e => e.id === providerId, + store + ), + store + ), + 5000 + ); + if (!result) { + throw new Error(`Timed out waiting for authentication provider '${providerId}' to register.`); + } + provider = this._authenticationProviders.get(result.id); + if (provider) { + return provider; + } + throw new Error(`No authentication provider '${providerId}' is currently registered.`); + } finally { + store.dispose(); + } } } diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts similarity index 99% rename from src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts rename to src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts index 2c6f4addc8e..2c01f6fd5cf 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts @@ -7,7 +7,7 @@ import { localize } from '../../../../nls.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IWorkbenchLayoutService } from '../../layout/browser/layoutService.js'; import { AuxiliaryWindow, AuxiliaryWindowMode, BrowserAuxiliaryWindowService, IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from '../browser/auxiliaryWindowService.js'; -import { ISandboxGlobals } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ISandboxGlobals } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; @@ -19,7 +19,7 @@ import { ShutdownReason } from '../../lifecycle/common/lifecycle.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { Barrier } from '../../../../base/common/async.js'; import { IHostService } from '../../host/browser/host.js'; -import { applyZoom } from '../../../../platform/window/electron-sandbox/window.js'; +import { applyZoom } from '../../../../platform/window/electron-browser/window.js'; import { getZoomLevel, isFullscreen, setFullscreen } from '../../../../base/browser/browser.js'; import { getActiveWindow } from '../../../../base/browser/dom.js'; import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; diff --git a/src/vs/workbench/services/browserElements/electron-sandbox/browserElementsService.ts b/src/vs/workbench/services/browserElements/electron-browser/browserElementsService.ts similarity index 97% rename from src/vs/workbench/services/browserElements/electron-sandbox/browserElementsService.ts rename to src/vs/workbench/services/browserElements/electron-browser/browserElementsService.ts index 0f28cde37f5..b2aae31f500 100644 --- a/src/vs/workbench/services/browserElements/electron-sandbox/browserElementsService.ts +++ b/src/vs/workbench/services/browserElements/electron-browser/browserElementsService.ts @@ -5,12 +5,12 @@ import { BrowserType, IElementData, INativeBrowserElementsService } from '../../../../platform/browserElements/common/browserElements.js'; import { IRectangle } from '../../../../platform/window/common/window.js'; -import { ipcRenderer } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ipcRenderer } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { IBrowserElementsService } from '../browser/browserElementsService.js'; import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { NativeBrowserElementsService } from '../../../../platform/browserElements/common/nativeBrowserElementsService.js'; class WorkbenchNativeBrowserElementsService extends NativeBrowserElementsService { diff --git a/src/vs/workbench/services/checksum/electron-sandbox/checksumService.ts b/src/vs/workbench/services/checksum/electron-browser/checksumService.ts similarity index 92% rename from src/vs/workbench/services/checksum/electron-sandbox/checksumService.ts rename to src/vs/workbench/services/checksum/electron-browser/checksumService.ts index 57824e93880..93b88334fba 100644 --- a/src/vs/workbench/services/checksum/electron-sandbox/checksumService.ts +++ b/src/vs/workbench/services/checksum/electron-browser/checksumService.ts @@ -4,6 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IChecksumService } from '../../../../platform/checksum/common/checksumService.js'; -import { registerSharedProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; registerSharedProcessRemoteService(IChecksumService, 'checksum'); diff --git a/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts b/src/vs/workbench/services/clipboard/electron-browser/clipboardService.ts similarity index 100% rename from src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts rename to src/vs/workbench/services/clipboard/electron-browser/clipboardService.ts diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index c56106ddd6a..e3715ce5097 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -332,10 +332,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } updateValue(key: string, value: any): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; - updateValue(key: string, value: any, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise; - async updateValue(key: string, value: any, arg3?: any, arg4?: any, options?: any): Promise { + updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; + updateValue(key: string, value: unknown, target: ConfigurationTarget): Promise; + updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise; + async updateValue(key: string, value: unknown, arg3?: any, arg4?: any, options?: any): Promise { const overrides: IConfigurationUpdateOverrides | undefined = isConfigurationUpdateOverrides(arg3) ? arg3 : isConfigurationOverrides(arg3) ? { resource: arg3.resource, overrideIdentifiers: arg3.overrideIdentifier ? [arg3.overrideIdentifier] : undefined } : undefined; const target: ConfigurationTarget | undefined = overrides ? arg4 : arg3; @@ -996,7 +996,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return validWorkspaceFolders; } - private async writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationUpdateOverrides | undefined, options?: IConfigurationUpdateOverrides): Promise { + private async writeConfigurationValue(key: string, value: unknown, target: ConfigurationTarget, overrides: IConfigurationUpdateOverrides | undefined, options?: IConfigurationUpdateOverrides): Promise { if (!this.instantiationService) { throw new Error('Cannot write configuration because the configuration service is not yet ready to accept writes.'); } @@ -1080,7 +1080,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - private deriveConfigurationTargets(key: string, value: any, inspect: IConfigurationValue): ConfigurationTarget[] { + private deriveConfigurationTargets(key: string, value: unknown, inspect: IConfigurationValue): ConfigurationTarget[] { if (equals(value, inspect.value)) { return []; } diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 18f3c329745..1cbb3f351a6 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -112,7 +112,7 @@ export class ConfigurationEditingError extends ErrorNoTelemetry { export interface IConfigurationValue { key: string; - value: any; + value: unknown; } export interface IConfigurationEditingOptions extends IConfigurationUpdateOptions { diff --git a/src/vs/workbench/services/configuration/common/jsonEditing.ts b/src/vs/workbench/services/configuration/common/jsonEditing.ts index 41481d442c7..0dcef249fb7 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditing.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditing.ts @@ -25,7 +25,7 @@ export class JSONEditingError extends Error { export interface IJSONValue { path: JSONPath; - value: any; + value: unknown; } export interface IJSONEditingService { diff --git a/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts index c192b3a38a7..2c96891407d 100644 --- a/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts @@ -143,14 +143,14 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR this.resolvableVariables.add('input'); } - override async resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise { + override async resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: unknown, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise { const parsed = ConfigurationResolverExpression.parse(config); await this.resolveWithInteraction(folder, parsed, section, variables, target); return parsed.toObject(); } - override async resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: any, section?: string, variableToCommandMap?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined> { + override async resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: unknown, section?: string, variableToCommandMap?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined> { const expr = ConfigurationResolverExpression.parse(config); // Get values for input variables from UI diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index 333de5abbd7..15f7c61d9b6 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -34,13 +34,13 @@ export interface IConfigurationResolverService { * @param section For example, 'tasks' or 'debug'. Used for resolving inputs. * @param variables Aliases for commands. */ - resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise; + resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: unknown, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise; /** * Similar to resolveWithInteractionReplace, except without the replace. Returns a map of variables and their resolution. * Keys in the map will be of the format input:variableName or command:variableName. */ - resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined>; + resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: unknown, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined>; /** * Contributes a variable that can be resolved later. Consumers that use resolveAny, resolveWithInteraction, diff --git a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts similarity index 96% rename from src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts rename to src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts index 0e228ecb9f6..f169e776482 100644 --- a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; @@ -13,7 +13,7 @@ import { IConfigurationResolverService } from '../common/configurationResolver.j import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { BaseConfigurationResolverService } from '../browser/baseConfigurationResolverService.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; -import { IShellEnvironmentService } from '../../environment/electron-sandbox/shellEnvironmentService.js'; +import { IShellEnvironmentService } from '../../environment/electron-browser/shellEnvironmentService.js'; import { IPathService } from '../../path/common/pathService.js'; import { IExtensionService } from '../../extensions/common/extensions.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; diff --git a/src/vs/workbench/services/configurationResolver/test/electron-sandbox/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts similarity index 100% rename from src/vs/workbench/services/configurationResolver/test/electron-sandbox/configurationResolverService.test.ts rename to src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts similarity index 99% rename from src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts rename to src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index a7999fb3b67..fb971f742d5 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -14,7 +14,7 @@ import { INotificationService } from '../../../../platform/notification/common/n import { IContextMenuDelegate, IContextMenuEvent } from '../../../../base/browser/contextmenu.js'; import { createSingleCallFunction } from '../../../../base/common/functional.js'; import { IContextMenuItem } from '../../../../base/parts/contextmenu/common/contextmenu.js'; -import { popup } from '../../../../base/parts/contextmenu/electron-sandbox/contextmenu.js'; +import { popup } from '../../../../base/parts/contextmenu/electron-browser/contextmenu.js'; import { hasNativeContextMenu, MenuSettings } from '../../../../platform/window/common/window.js'; import { isMacintosh, isWindows } from '../../../../base/common/platform.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts similarity index 100% rename from src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts rename to src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts diff --git a/src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts b/src/vs/workbench/services/dialogs/test/electron-browser/fileDialogService.test.ts similarity index 99% rename from src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts rename to src/vs/workbench/services/dialogs/test/electron-browser/fileDialogService.test.ts index f75cd47844e..f97f6a1d418 100644 --- a/src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts +++ b/src/vs/workbench/services/dialogs/test/electron-browser/fileDialogService.test.ts @@ -25,7 +25,7 @@ import { IOpenerService } from '../../../../../platform/opener/common/opener.js' import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IWorkspacesService } from '../../../../../platform/workspaces/common/workspaces.js'; import { ISimpleFileDialog } from '../../browser/simpleFileDialog.js'; -import { FileDialogService } from '../../electron-sandbox/fileDialogService.js'; +import { FileDialogService } from '../../electron-browser/fileDialogService.js'; import { IEditorService } from '../../../editor/common/editorService.js'; import { BrowserWorkbenchEnvironmentService } from '../../../environment/browser/environmentService.js'; import { IWorkbenchEnvironmentService } from '../../../environment/common/environmentService.js'; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 18b79251200..57ae575b1be 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -861,7 +861,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { else { const result: IEditorIdentifier[] = []; - for (const group of this.editorGroupsContainer.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + for (const group of this.editorGroupsContainer.getGroups(options?.order === EditorsOrder.SEQUENTIAL ? GroupsOrder.GRID_APPEARANCE : GroupsOrder.MOST_RECENTLY_ACTIVE)) { const editors: EditorInput[] = []; // Resource provided: result is an array diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 0bed762abec..767a8628adc 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -341,7 +341,7 @@ export interface IEditorService { /** * Save the provided list of editors. */ - save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise; + save(editors: IEditorIdentifier | readonly IEditorIdentifier[], options?: ISaveEditorsOptions): Promise; /** * Save all editors. @@ -353,7 +353,7 @@ export interface IEditorService { * * @returns `true` if all editors reverted and `false` otherwise. */ - revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; + revert(editors: IEditorIdentifier | readonly IEditorIdentifier[], options?: IRevertOptions): Promise; /** * Reverts all editors. diff --git a/src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts b/src/vs/workbench/services/encryption/electron-browser/encryptionService.ts similarity index 93% rename from src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts rename to src/vs/workbench/services/encryption/electron-browser/encryptionService.ts index 8847433ce45..38d746bcd1f 100644 --- a/src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts +++ b/src/vs/workbench/services/encryption/electron-browser/encryptionService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { IEncryptionService } from '../../../../platform/encryption/common/encryptionService.js'; registerMainProcessRemoteService(IEncryptionService, 'encryption'); diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts similarity index 100% rename from src/vs/workbench/services/environment/electron-sandbox/environmentService.ts rename to src/vs/workbench/services/environment/electron-browser/environmentService.ts diff --git a/src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts b/src/vs/workbench/services/environment/electron-browser/shellEnvironmentService.ts similarity index 98% rename from src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts rename to src/vs/workbench/services/environment/electron-browser/shellEnvironmentService.ts index a05b8eacd24..b8108603fff 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/shellEnvironmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/shellEnvironmentService.ts @@ -5,7 +5,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IProcessEnvironment } from '../../../../base/common/platform.js'; -import { process } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { process } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; export const IShellEnvironmentService = createDecorator('shellEnvironmentService'); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionGalleryManifestService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionGalleryManifestService.ts similarity index 99% rename from src/vs/workbench/services/extensionManagement/electron-sandbox/extensionGalleryManifestService.ts rename to src/vs/workbench/services/extensionManagement/electron-browser/extensionGalleryManifestService.ts index 4e494937b12..92d78c4f30f 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionGalleryManifestService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionGalleryManifestService.ts @@ -15,7 +15,7 @@ import { ExtensionGalleryManifestService as ExtensionGalleryManifestService } fr import { resolveMarketplaceHeaders } from '../../../../platform/externalServices/common/marketplace.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { asJson, IRequestService } from '../../../../platform/request/common/request.js'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts similarity index 99% rename from src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts rename to src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts index eaf010aafef..c76644b7c28 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts @@ -8,7 +8,7 @@ import { Schemas } from '../../../../base/common/network.js'; import { ExtensionInstallLocation, IExtensionManagementServer, IExtensionManagementServerService } from '../common/extensionManagement.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; import { IChannel } from '../../../../base/parts/ipc/common/ipc.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { NativeRemoteExtensionManagementService } from './remoteExtensionManagementService.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementService.ts similarity index 99% rename from src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts rename to src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementService.ts index eeae819a083..68eddb835ba 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementService.ts @@ -13,7 +13,7 @@ import { Schemas } from '../../../../base/common/network.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IDownloadService } from '../../../../platform/download/common/download.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { joinPath } from '../../../../base/common/resources.js'; import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts similarity index 98% rename from src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts rename to src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts index f27ddc63799..b20e96c6a97 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { IChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { IExtensionTipsService, IExecutableBasedExtensionTip, IConfigBasedExtensionTip } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { URI } from '../../../../base/common/uri.js'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/nativeExtensionManagementService.ts similarity index 98% rename from src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts rename to src/vs/workbench/services/extensionManagement/electron-browser/nativeExtensionManagementService.ts index b45ca0bcce6..cb1e06e97c8 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/nativeExtensionManagementService.ts @@ -17,7 +17,7 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { ProfileAwareExtensionManagementChannelClient } from '../common/extensionManagementChannelClient.js'; import { ExtensionIdentifier, ExtensionType, isResolverExtension } from '../../../../platform/extensions/common/extensions.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; export class NativeExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService.ts similarity index 100% rename from src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts rename to src/vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService.ts diff --git a/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts rename to src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts rename to src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostStarter.ts similarity index 93% rename from src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts rename to src/vs/workbench/services/extensions/electron-browser/extensionHostStarter.ts index e8bcaabe8eb..cc0b19746e7 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostStarter.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from '../../../../platform/extensions/common/extensionHostStarter.js'; registerMainProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName); diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionsScannerService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionsScannerService.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-sandbox/extensionsScannerService.ts rename to src/vs/workbench/services/extensions/electron-browser/extensionsScannerService.ts diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts similarity index 99% rename from src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts rename to src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 41b8bc38af9..803a4fd75db 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -16,7 +16,7 @@ import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js'; import { BufferedEmitter } from '../../../../base/parts/ipc/common/ipc.net.js'; -import { acquirePort } from '../../../../base/parts/ipc/electron-sandbox/ipc.mp.js'; +import { acquirePort } from '../../../../base/parts/ipc/electron-browser/ipc.mp.js'; import * as nls from '../../../../nls.js'; import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from '../../../../platform/extensions/common/extensionHostStarter.js'; @@ -29,8 +29,8 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { isLoggingOnly } from '../../../../platform/telemetry/common/telemetryUtils.js'; import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; import { IWorkspaceContextService, WorkbenchState, isUntitledWorkspace } from '../../../../platform/workspace/common/workspace.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; -import { IShellEnvironmentService } from '../../environment/electron-sandbox/shellEnvironmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; +import { IShellEnvironmentService } from '../../environment/electron-browser/shellEnvironmentService.js'; import { MessagePortExtHostConnection, writeExtHostConnection } from '../common/extensionHostEnv.js'; import { IExtensionHostInitData, MessageType, NativeLogMarkers, UIKind, isMessageOfType } from '../common/extensionHostProtocol.js'; import { LocalProcessRunningLocation } from '../common/extensionRunningLocation.js'; diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts rename to src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts diff --git a/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts b/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts similarity index 99% rename from src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts rename to src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts index 85b2f1eb68a..5c0af31e6b3 100644 --- a/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts @@ -16,7 +16,7 @@ import { DiskFileSystemProviderClient, LOCAL_FILE_SYSTEM_CHANNEL_NAME } from '.. import { ILogMessage, AbstractUniversalWatcherClient } from '../../../../platform/files/common/watcher.js'; import { UniversalWatcherClient } from './watcherClient.js'; import { ILoggerService, ILogService } from '../../../../platform/log/common/log.js'; -import { IUtilityProcessWorkerWorkbenchService } from '../../utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.js'; +import { IUtilityProcessWorkerWorkbenchService } from '../../utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.js'; import { LogService } from '../../../../platform/log/common/logService.js'; /** diff --git a/src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts b/src/vs/workbench/services/files/electron-browser/elevatedFileService.ts similarity index 98% rename from src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts rename to src/vs/workbench/services/files/electron-browser/elevatedFileService.ts index 158a5ac0f4c..19c2a1c0640 100644 --- a/src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/elevatedFileService.ts @@ -12,7 +12,7 @@ import { IFileService, IFileStatWithMetadata, IWriteFileOptions } from '../../.. import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IElevatedFileService } from '../common/elevatedFileService.js'; import { isWindows } from '../../../../base/common/platform.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; diff --git a/src/vs/workbench/services/files/electron-sandbox/watcherClient.ts b/src/vs/workbench/services/files/electron-browser/watcherClient.ts similarity index 97% rename from src/vs/workbench/services/files/electron-sandbox/watcherClient.ts rename to src/vs/workbench/services/files/electron-browser/watcherClient.ts index 2c8554796b3..39bcfbd0bbe 100644 --- a/src/vs/workbench/services/files/electron-sandbox/watcherClient.ts +++ b/src/vs/workbench/services/files/electron-browser/watcherClient.ts @@ -7,7 +7,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { getDelayedChannel, ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { IFileChange } from '../../../../platform/files/common/files.js'; import { AbstractUniversalWatcherClient, ILogMessage, IRecursiveWatcher } from '../../../../platform/files/common/watcher.js'; -import { IUtilityProcessWorkerWorkbenchService } from '../../utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.js'; +import { IUtilityProcessWorkerWorkbenchService } from '../../utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.js'; export class UniversalWatcherClient extends AbstractUniversalWatcherClient { diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-browser/nativeHostService.ts similarity index 99% rename from src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts rename to src/vs/workbench/services/host/electron-browser/nativeHostService.ts index f3d99829c08..cb7dcc810e2 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/nativeHostService.ts @@ -12,7 +12,7 @@ import { IWorkbenchEnvironmentService } from '../../environment/common/environme import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions, IPoint, IRectangle } from '../../../../platform/window/common/window.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { NativeHostService } from '../../../../platform/native/common/nativeHostService.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js'; import { disposableWindowInterval, getActiveDocument, getWindowId, getWindowsCount, hasWindow, onDidRegisterWindow } from '../../../../base/browser/dom.js'; import { memoize } from '../../../../base/common/decorators.js'; diff --git a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts b/src/vs/workbench/services/integrity/electron-browser/integrityService.ts similarity index 100% rename from src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts rename to src/vs/workbench/services/integrity/electron-browser/integrityService.ts diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index bb41a6d5705..c5a78e509f1 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -10,6 +10,7 @@ import * as browser from '../../../../base/browser/browser.js'; import { BrowserFeatures, KeyboardSupport } from '../../../../base/browser/canIUse.js'; import * as dom from '../../../../base/browser/dom.js'; import { printKeyboardEvent, printStandardKeyboardEvent, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { mainWindow } from '../../../../base/browser/window.js'; import { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { parse } from '../../../../base/common/json.js'; @@ -18,13 +19,13 @@ import { UserSettingsLabelProvider } from '../../../../base/common/keybindingLab import { KeybindingParser } from '../../../../base/common/keybindingParser.js'; import { Keybinding, KeyCodeChord, ResolvedKeybinding, ScanCodeChord } from '../../../../base/common/keybindings.js'; import { IMMUTABLE_CODE_TO_KEY_CODE, KeyCode, KeyCodeUtils, KeyMod, ScanCode, ScanCodeUtils } from '../../../../base/common/keyCodes.js'; -import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import * as objects from '../../../../base/common/objects.js'; import { isMacintosh, OperatingSystem, OS } from '../../../../base/common/platform.js'; import { dirname } from '../../../../base/common/resources.js'; -import { mainWindow } from '../../../../base/browser/window.js'; // platform +import { ILocalizedString, isLocalizedString } from '../../../../platform/action/common/action.js'; import { MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -44,17 +45,17 @@ import { INotificationService } from '../../../../platform/notification/common/n import { Registry } from '../../../../platform/registry/common/platform.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; -import { ILocalizedString, isLocalizedString } from '../../../../platform/action/common/action.js'; // workbench +import { remove } from '../../../../base/common/arrays.js'; import { commandsExtensionPoint } from '../../actions/common/menusExtensionPoint.js'; import { IExtensionService } from '../../extensions/common/extensions.js'; import { ExtensionMessageCollector, ExtensionsRegistry } from '../../extensions/common/extensionsRegistry.js'; import { IHostService } from '../../host/browser/host.js'; +import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js'; +import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from '../common/keybindingIO.js'; import { IKeyboard, INavigatorWithKeyboard } from './navigatorKeyboard.js'; import { getAllUnboundCommands } from './unboundCommands.js'; -import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from '../common/keybindingIO.js'; -import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js'; interface ContributedKeyBinding { command: string; @@ -184,7 +185,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private userKeybindings: UserKeybindings; private isComposingGlobalContextKey: IContextKey; private _keybindingHoldMode: DeferredPromise | null; - private readonly _contributions: KeybindingsSchemaContribution[] = []; + private readonly _contributions: Array<{ + readonly listener?: IDisposable; + readonly contribution: KeybindingsSchemaContribution; + }> = []; private readonly kbsJsonSchema: KeybindingsJsonSchema; constructor( @@ -266,6 +270,13 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); } + public override dispose(): void { + this._contributions.forEach(c => c.listener?.dispose()); + this._contributions.length = 0; + + super.dispose(); + } + private _registerKeyListeners(window: Window): IDisposable { const disposables = new DisposableStore(); @@ -300,16 +311,22 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return disposables; } - public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { - this._contributions.push(contribution); - if (contribution.onDidChange) { - this._register(contribution.onDidChange(() => this.updateKeybindingsJsonSchema())); - } + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): IDisposable { + const listener = contribution.onDidChange?.(() => this.updateKeybindingsJsonSchema()); + const entry = { listener, contribution }; + this._contributions.push(entry); + this.updateKeybindingsJsonSchema(); + + return toDisposable(() => { + listener?.dispose(); + remove(this._contributions, entry); + this.updateKeybindingsJsonSchema(); + }); } private updateKeybindingsJsonSchema() { - this.kbsJsonSchema.updateSchema(this._contributions.flatMap(x => x.getSchemaAdditions())); + this.kbsJsonSchema.updateSchema(this._contributions.flatMap(x => x.contribution.getSchemaAdditions())); } private _printKeybinding(keybinding: Keybinding): string { diff --git a/src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeyboardLayout.ts similarity index 100% rename from src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts rename to src/vs/workbench/services/keybinding/electron-browser/nativeKeyboardLayout.ts diff --git a/src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayoutService.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeyboardLayoutService.ts similarity index 100% rename from src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayoutService.ts rename to src/vs/workbench/services/keybinding/electron-browser/nativeKeyboardLayoutService.ts diff --git a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts similarity index 99% rename from src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts rename to src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index b7fe1aadac3..762840ba2f2 100644 --- a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -6,7 +6,7 @@ import { handleVetos } from '../../../../platform/lifecycle/common/lifecycle.js'; import { ShutdownReason, ILifecycleService, IWillShutdownEventJoiner, WillShutdownJoinerOrder } from '../common/lifecycle.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { ipcRenderer } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { ipcRenderer } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { AbstractLifecycleService } from '../common/lifecycleService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; diff --git a/src/vs/workbench/services/lifecycle/test/electron-sandbox/lifecycleService.test.ts b/src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts similarity index 98% rename from src/vs/workbench/services/lifecycle/test/electron-sandbox/lifecycleService.test.ts rename to src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts index 7afb9dd0ab0..fe043e7004d 100644 --- a/src/vs/workbench/services/lifecycle/test/electron-sandbox/lifecycleService.test.ts +++ b/src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts @@ -9,8 +9,8 @@ import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { ShutdownReason, WillShutdownJoinerOrder } from '../../common/lifecycle.js'; -import { NativeLifecycleService } from '../../electron-sandbox/lifecycleService.js'; -import { workbenchInstantiationService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { NativeLifecycleService } from '../../electron-browser/lifecycleService.js'; +import { workbenchInstantiationService } from '../../../../test/electron-browser/workbenchTestServices.js'; suite('Lifecycleservice', function () { diff --git a/src/vs/workbench/services/localization/electron-sandbox/languagePackService.ts b/src/vs/workbench/services/localization/electron-browser/languagePackService.ts similarity index 93% rename from src/vs/workbench/services/localization/electron-sandbox/languagePackService.ts rename to src/vs/workbench/services/localization/electron-browser/languagePackService.ts index 5c07673518d..a66deb42f13 100644 --- a/src/vs/workbench/services/localization/electron-sandbox/languagePackService.ts +++ b/src/vs/workbench/services/localization/electron-browser/languagePackService.ts @@ -4,6 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { ILanguagePackService } from '../../../../platform/languagePacks/common/languagePacks.js'; -import { registerSharedProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; registerSharedProcessRemoteService(ILanguagePackService, 'languagePacks'); diff --git a/src/vs/workbench/services/localization/electron-sandbox/localeService.ts b/src/vs/workbench/services/localization/electron-browser/localeService.ts similarity index 100% rename from src/vs/workbench/services/localization/electron-sandbox/localeService.ts rename to src/vs/workbench/services/localization/electron-browser/localeService.ts diff --git a/src/vs/workbench/services/log/electron-sandbox/logService.ts b/src/vs/workbench/services/log/electron-browser/logService.ts similarity index 97% rename from src/vs/workbench/services/log/electron-sandbox/logService.ts rename to src/vs/workbench/services/log/electron-browser/logService.ts index 34245d85ff0..97ae7dbbf90 100644 --- a/src/vs/workbench/services/log/electron-sandbox/logService.ts +++ b/src/vs/workbench/services/log/electron-browser/logService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ConsoleLogger, ILogger } from '../../../../platform/log/common/log.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { LoggerChannelClient } from '../../../../platform/log/common/logIpc.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { windowLogGroup, windowLogId } from '../common/logConstants.js'; diff --git a/src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts b/src/vs/workbench/services/menubar/electron-browser/menubarService.ts similarity index 88% rename from src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts rename to src/vs/workbench/services/menubar/electron-browser/menubarService.ts index 7b00164b381..bd5d8836993 100644 --- a/src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts +++ b/src/vs/workbench/services/menubar/electron-browser/menubarService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMenubarService } from '../../../../platform/menubar/electron-sandbox/menubar.js'; -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { IMenubarService } from '../../../../platform/menubar/electron-browser/menubar.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; registerMainProcessRemoteService(IMenubarService, 'menubar'); diff --git a/src/vs/workbench/services/path/electron-sandbox/pathService.ts b/src/vs/workbench/services/path/electron-browser/pathService.ts similarity index 96% rename from src/vs/workbench/services/path/electron-sandbox/pathService.ts rename to src/vs/workbench/services/path/electron-browser/pathService.ts index b2fbd6d92dd..5adc3bb3c6b 100644 --- a/src/vs/workbench/services/path/electron-sandbox/pathService.ts +++ b/src/vs/workbench/services/path/electron-browser/pathService.ts @@ -5,7 +5,7 @@ import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IPathService, AbstractPathService } from '../common/pathService.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; diff --git a/src/vs/workbench/services/process/electron-sandbox/processService.ts b/src/vs/workbench/services/process/electron-browser/processService.ts similarity index 93% rename from src/vs/workbench/services/process/electron-sandbox/processService.ts rename to src/vs/workbench/services/process/electron-browser/processService.ts index e47499664b7..1d9b8f2b17f 100644 --- a/src/vs/workbench/services/process/electron-sandbox/processService.ts +++ b/src/vs/workbench/services/process/electron-browser/processService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { IProcessService } from '../../../../platform/process/common/process.js'; registerMainProcessRemoteService(IProcessService, 'process'); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentService.ts similarity index 100% rename from src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts rename to src/vs/workbench/services/remote/electron-browser/remoteAgentService.ts diff --git a/src/vs/workbench/services/request/electron-sandbox/requestService.ts b/src/vs/workbench/services/request/electron-browser/requestService.ts similarity index 100% rename from src/vs/workbench/services/request/electron-sandbox/requestService.ts rename to src/vs/workbench/services/request/electron-browser/requestService.ts diff --git a/src/vs/workbench/services/search/common/getFileResults.ts b/src/vs/workbench/services/search/common/getFileResults.ts index 88b24b6568b..d228b748575 100644 --- a/src/vs/workbench/services/search/common/getFileResults.ts +++ b/src/vs/workbench/services/search/common/getFileResults.ts @@ -30,16 +30,16 @@ export const getFileResults = ( const results: ITextSearchResult[] = []; - const patternIndecies: { matchStartIndex: number; matchedText: string }[] = []; + const patternIndices: { matchStartIndex: number; matchedText: string }[] = []; let patternMatch: RegExpExecArray | null = null; let remainingResultQuota = options.remainingResultQuota; while (remainingResultQuota >= 0 && (patternMatch = pattern.exec(text))) { - patternIndecies.push({ matchStartIndex: patternMatch.index, matchedText: patternMatch[0] }); + patternIndices.push({ matchStartIndex: patternMatch.index, matchedText: patternMatch[0] }); remainingResultQuota--; } - if (patternIndecies.length) { + if (patternIndices.length) { const contextLinesNeeded = new Set(); const resultLines = new Set(); @@ -56,7 +56,7 @@ export const getFileResults = ( if (prevLineEnd < text.length) { lineRanges.push({ start: prevLineEnd, end: text.length }); } let startLine = 0; - for (const { matchStartIndex, matchedText } of patternIndecies) { + for (const { matchStartIndex, matchedText } of patternIndices) { if (remainingResultQuota < 0) { break; } diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index feaaf16c7f4..b75fb3f366a 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -12,7 +12,7 @@ import { Schemas } from '../../../../base/common/network.js'; import * as path from '../../../../base/common/path.js'; import { isEqual, basename, relativePath, isAbsolutePath } from '../../../../base/common/resources.js'; import * as strings from '../../../../base/common/strings.js'; -import { assertIsDefined, isDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined, isDefined } from '../../../../base/common/types.js'; import { URI, URI as uri, UriComponents } from '../../../../base/common/uri.js'; import { isMultilineRegexSource } from '../../../../editor/common/model/textModelSearch.js'; import * as nls from '../../../../nls.js'; @@ -313,7 +313,7 @@ export class QueryBuilder { } const relPath = path.relative(searchRoot.fsPath, file.fsPath); - assertIsDefined(folderQuery.includePattern)[relPath.replace(/\\/g, '/')] = true; + assertReturnsDefined(folderQuery.includePattern)[relPath.replace(/\\/g, '/')] = true; } else { if (file.fsPath) { hasIncludedFile = true; diff --git a/src/vs/workbench/services/search/electron-sandbox/searchService.ts b/src/vs/workbench/services/search/electron-browser/searchService.ts similarity index 100% rename from src/vs/workbench/services/search/electron-sandbox/searchService.ts rename to src/vs/workbench/services/search/electron-browser/searchService.ts diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 85ef0f3dbc7..da33ca6c132 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import * as path from '../../../../../base/common/path.js'; import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import * as glob from '../../../../../base/common/glob.js'; -import { URI } from '../../../../../base/common/uri.js'; -import { deserializeSearchError, IFolderQuery, ISearchRange, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode, ISerializedFileMatch } from '../../common/search.js'; -import { TextSearchEngineAdapter } from '../../node/textSearchAdapter.js'; -import { flakySuite } from '../../../../../base/test/node/testUtils.js'; import { FileAccess } from '../../../../../base/common/network.js'; +import * as path from '../../../../../base/common/path.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { flakySuite } from '../../../../../base/test/node/testUtils.js'; +import { deserializeSearchError, IFolderQuery, ISearchRange, ISerializedFileMatch, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode } from '../../common/search.js'; +import { TextSearchEngineAdapter } from '../../node/textSearchAdapter.js'; const TEST_FIXTURES = path.normalize(FileAccess.asFileUri('vs/workbench/services/search/test/node/fixtures').fsPath); const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); @@ -405,7 +405,7 @@ flakySuite('TextSearch-integration', function () { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err); - const regexParseErrorForLookAround = 'Regex parse error: lookbehind assertion is not fixed length'; + const regexParseErrorForLookAround = 'Regex parse error: length of lookbehind assertion is not limited'; assert.strictEqual(searchError.message, regexParseErrorForLookAround); assert.strictEqual(searchError.code, SearchErrorCode.regexParseError); }); diff --git a/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts b/src/vs/workbench/services/secrets/electron-browser/secretStorageService.ts similarity index 100% rename from src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts rename to src/vs/workbench/services/secrets/electron-browser/secretStorageService.ts diff --git a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts similarity index 98% rename from src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts rename to src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts index 8110886c226..ed735cbe689 100644 --- a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService.ts +++ b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts @@ -7,11 +7,11 @@ import { Client as MessagePortClient } from '../../../../base/parts/ipc/common/i import { IChannel, IServerChannel, getDelayedChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { SharedProcessChannelConnection, SharedProcessRawConnection } from '../../../../platform/sharedProcess/common/sharedProcess.js'; import { mark } from '../../../../base/common/performance.js'; import { Barrier, timeout } from '../../../../base/common/async.js'; -import { acquirePort } from '../../../../base/parts/ipc/electron-sandbox/ipc.mp.js'; +import { acquirePort } from '../../../../base/parts/ipc/electron-browser/ipc.mp.js'; export class SharedProcessService extends Disposable implements ISharedProcessService { diff --git a/src/vs/workbench/services/storage/browser/storageService.ts b/src/vs/workbench/services/storage/browser/storageService.ts index ca10b6f7d95..309033e3b9a 100644 --- a/src/vs/workbench/services/storage/browser/storageService.ts +++ b/src/vs/workbench/services/storage/browser/storageService.ts @@ -11,7 +11,7 @@ import { DeferredPromise, Promises } from '../../../../base/common/async.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { Emitter } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { InMemoryStorageDatabase, isStorageItemsChangeEvent, IStorage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from '../../../../base/parts/storage/common/storage.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { AbstractStorageService, isProfileUsingDefaultStorage, IS_NEW_KEY, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -168,7 +168,7 @@ export class BrowserStorageService extends AbstractStorageService { return; } - const oldProfileStorage = assertIsDefined(this.profileStorage); + const oldProfileStorage = assertReturnsDefined(this.profileStorage); const oldItems = oldProfileStorage.items; // Close old profile storage but only if this is @@ -181,7 +181,7 @@ export class BrowserStorageService extends AbstractStorageService { await this.createProfileStorage(toProfile); // Handle data switch and eventing - this.switchData(oldItems, assertIsDefined(this.profileStorage), StorageScope.PROFILE); + this.switchData(oldItems, assertReturnsDefined(this.profileStorage), StorageScope.PROFILE); } protected async switchToWorkspace(toWorkspace: IAnyWorkspaceIdentifier, preserveData: boolean): Promise { diff --git a/src/vs/workbench/services/storage/electron-sandbox/storageService.ts b/src/vs/workbench/services/storage/electron-browser/storageService.ts similarity index 100% rename from src/vs/workbench/services/storage/electron-sandbox/storageService.ts rename to src/vs/workbench/services/storage/electron-browser/storageService.ts diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts similarity index 97% rename from src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts rename to src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index e49eaefbbab..75d61ed72d7 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -7,16 +7,16 @@ import { ITelemetryService, ITelemetryData, TelemetryLevel } from '../../../../p import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from '../../../../platform/telemetry/common/telemetryUtils.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { TelemetryAppenderClient } from '../../../../platform/telemetry/common/telemetryIpc.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { resolveWorkbenchCommonProperties } from '../common/workbenchCommonProperties.js'; import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from '../../../../platform/telemetry/common/telemetryService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ClassifiedEvent, StrictPropertyCheck, OmitMetadata, IGDPRProperty } from '../../../../platform/telemetry/common/gdprTypings.js'; -import { process } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { process } from '../../../../base/parts/sandbox/electron-browser/globals.js'; export class TelemetryService extends Disposable implements ITelemetryService { diff --git a/src/vs/workbench/services/terminal/common/embedderTerminalService.ts b/src/vs/workbench/services/terminal/common/embedderTerminalService.ts index e976a8617ca..f1001facbc1 100644 --- a/src/vs/workbench/services/terminal/common/embedderTerminalService.ts +++ b/src/vs/workbench/services/terminal/common/embedderTerminalService.ts @@ -118,6 +118,9 @@ class EmbedderTerminalProcess extends Disposable implements ITerminalChildProces input(): void { // not supported } + sendSignal(): void { + // not supported + } async processBinary(): Promise { // not supported } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c87a3d473b9..6ab27644450 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -7,7 +7,7 @@ import { localize } from '../../../../nls.js'; import { Emitter } from '../../../../base/common/event.js'; import { URI } from '../../../../base/common/uri.js'; import { mark } from '../../../../base/common/performance.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { EncodingMode, ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, TextFileResolveReason, ITextFileEditorModelSaveEvent, ITextFileSaveAsOptions } from './textfiles.js'; import { IRevertOptions, SaveReason, SaveSourceRegistry } from '../../../common/editor.js'; import { BaseTextEditorModel } from '../../../common/editor/textEditorModel.js'; @@ -929,7 +929,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // participant triggering progress.report({ message: localize('saveTextFile', "Writing into file...") }); this.trace(`doSave(${versionId}) - before write()`); - const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); + const lastResolvedFileStat = assertReturnsDefined(this.lastResolvedFileStat); const resolvedTextFileEditorModel = this; return this.saveSequentializer.run(versionId, (async () => { try { diff --git a/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts similarity index 99% rename from src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts rename to src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 913208b2221..79ef5c9ace6 100644 --- a/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -14,7 +14,7 @@ import { IUntitledTextEditorModelManager, IUntitledTextEditorService } from '../ import { ILifecycleService } from '../../lifecycle/common/lifecycle.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IModelService } from '../../../../editor/common/services/model.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IDialogService, IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IFilesConfigurationService } from '../../filesConfiguration/common/filesConfigurationService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 2a7acd660f7..cde036203e0 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../.. import { TextFileEditorModelManager } from '../../common/textFileEditorModelManager.js'; import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError } from '../../../../../platform/files/common/files.js'; import { DeferredPromise, timeout } from '../../../../../base/common/async.js'; -import { assertIsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { createTextBufferFactory } from '../../../../../editor/common/model/textModel.js'; import { DisposableStore, toDisposable } from '../../../../../base/common/lifecycle.js'; import { SaveReason, SaveSourceRegistry } from '../../../../common/editor.js'; @@ -611,7 +611,7 @@ suite('Files - TextFileEditorModel', () => { await model.resolve(); - const stat = assertIsDefined(getLastResolvedFileStat(model)); + const stat = assertReturnsDefined(getLastResolvedFileStat(model)); accessor.textFileService.setReadStreamErrorOnce(new NotModifiedSinceFileOperationError('error', { ...stat, mtime: stat.mtime - 1, readonly: !stat.readonly, locked: !stat.locked })); await model.resolve(); @@ -641,8 +641,8 @@ suite('Files - TextFileEditorModel', () => { model1.updateTextEditorModel(createTextBufferFactory('foo')); - const m1Mtime = assertIsDefined(getLastResolvedFileStat(model1)).mtime; - const m2Mtime = assertIsDefined(getLastResolvedFileStat(model2)).mtime; + const m1Mtime = assertReturnsDefined(getLastResolvedFileStat(model1)).mtime; + const m2Mtime = assertReturnsDefined(getLastResolvedFileStat(model2)).mtime; assert.ok(m1Mtime > 0); assert.ok(m2Mtime > 0); @@ -662,12 +662,12 @@ suite('Files - TextFileEditorModel', () => { // web tests does not ensure timeouts are respected at all, so we cannot // really assert the mtime to be different, only that it is equal or greater. // https://github.com/microsoft/vscode/issues/161886 - assert.ok(assertIsDefined(getLastResolvedFileStat(model1)).mtime >= m1Mtime); - assert.ok(assertIsDefined(getLastResolvedFileStat(model2)).mtime >= m2Mtime); + assert.ok(assertReturnsDefined(getLastResolvedFileStat(model1)).mtime >= m1Mtime); + assert.ok(assertReturnsDefined(getLastResolvedFileStat(model2)).mtime >= m2Mtime); } else { // on desktop we want to assert this condition more strictly though - assert.ok(assertIsDefined(getLastResolvedFileStat(model1)).mtime > m1Mtime); - assert.ok(assertIsDefined(getLastResolvedFileStat(model2)).mtime > m2Mtime); + assert.ok(assertReturnsDefined(getLastResolvedFileStat(model1)).mtime > m1Mtime); + assert.ok(assertReturnsDefined(getLastResolvedFileStat(model2)).mtime > m2Mtime); } }); diff --git a/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts similarity index 98% rename from src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.io.test.ts rename to src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts index 9be96d96683..0fbc74192de 100644 --- a/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts @@ -21,7 +21,7 @@ import { IWorkingCopyFileService, WorkingCopyFileService } from '../../../workin import { WorkingCopyService } from '../../../workingCopy/common/workingCopyService.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; import { TestInMemoryFileSystemProvider } from '../../../../test/browser/workbenchTestServices.js'; -import { TestNativeTextFileServiceWithEncodingOverrides, workbenchInstantiationService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestNativeTextFileServiceWithEncodingOverrides, workbenchInstantiationService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; suite('Files - NativeTextFileService i/o', function () { diff --git a/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts similarity index 98% rename from src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.test.ts rename to src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts index 91250289f3a..6128845b778 100644 --- a/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts @@ -12,7 +12,7 @@ import { ServiceCollection } from '../../../../../platform/instantiation/common/ import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { FileService } from '../../../../../platform/files/common/fileService.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; -import { TestNativeTextFileServiceWithEncodingOverrides, TestServiceAccessor, workbenchInstantiationService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestNativeTextFileServiceWithEncodingOverrides, TestServiceAccessor, workbenchInstantiationService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { IWorkingCopyFileService, WorkingCopyFileService } from '../../../workingCopy/common/workingCopyFileService.js'; import { WorkingCopyService } from '../../../workingCopy/common/workingCopyService.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index b4a84348fb7..09187c4940f 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -360,7 +360,7 @@ export class ThemeConfiguration { return ConfigurationTarget.USER; } - private async writeConfiguration(key: string, value: any, settingsTarget: ThemeSettingTarget): Promise { + private async writeConfiguration(key: string, value: unknown, settingsTarget: ThemeSettingTarget): Promise { if (settingsTarget === undefined || settingsTarget === 'preview') { return; } diff --git a/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts b/src/vs/workbench/services/themes/electron-browser/nativeHostColorSchemeService.ts similarity index 98% rename from src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts rename to src/vs/workbench/services/themes/electron-browser/nativeHostColorSchemeService.ts index b22de86f799..22cd3472570 100644 --- a/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/nativeHostColorSchemeService.ts @@ -8,7 +8,7 @@ import { INativeHostService } from '../../../../platform/native/common/native.js import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IHostColorSchemeService } from '../common/hostColorSchemeService.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { isBoolean, isObject } from '../../../../base/common/types.js'; import { IColorScheme } from '../../../../platform/window/common/window.js'; diff --git a/src/vs/workbench/services/themes/electron-sandbox/themes.contribution.ts b/src/vs/workbench/services/themes/electron-browser/themes.contribution.ts similarity index 100% rename from src/vs/workbench/services/themes/electron-sandbox/themes.contribution.ts rename to src/vs/workbench/services/themes/electron-browser/themes.contribution.ts diff --git a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts similarity index 98% rename from src/vs/workbench/services/timer/electron-sandbox/timerService.ts rename to src/vs/workbench/services/timer/electron-browser/timerService.ts index e992d1fb55c..a12cfdc7d42 100644 --- a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IExtensionService } from '../../extensions/common/extensions.js'; import { IUpdateService } from '../../../../platform/update/common/update.js'; @@ -13,7 +13,7 @@ import { IEditorService } from '../../editor/common/editorService.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { IStartupMetrics, AbstractTimerService, Writeable, ITimerService } from '../browser/timerService.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { process } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; +import { process } from '../../../../base/parts/sandbox/electron-browser/globals.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IWorkbenchLayoutService } from '../../layout/browser/layoutService.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; diff --git a/src/vs/workbench/services/title/electron-sandbox/titleService.ts b/src/vs/workbench/services/title/electron-browser/titleService.ts similarity index 90% rename from src/vs/workbench/services/title/electron-sandbox/titleService.ts rename to src/vs/workbench/services/title/electron-browser/titleService.ts index 00244e14a85..a7d742993ce 100644 --- a/src/vs/workbench/services/title/electron-sandbox/titleService.ts +++ b/src/vs/workbench/services/title/electron-browser/titleService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { NativeTitleService } from '../../../electron-sandbox/parts/titlebar/titlebarPart.js'; +import { NativeTitleService } from '../../../electron-browser/parts/titlebar/titlebarPart.js'; import { ITitleService } from '../browser/titleService.js'; registerSingleton(ITitleService, NativeTitleService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts b/src/vs/workbench/services/tunnel/electron-browser/tunnelService.ts similarity index 99% rename from src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts rename to src/vs/workbench/services/tunnel/electron-browser/tunnelService.ts index 0706995795a..f16f1c30360 100644 --- a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/electron-browser/tunnelService.ts @@ -14,7 +14,7 @@ import { ISharedProcessTunnelService } from '../../../../platform/remote/common/ import { ILifecycleService } from '../../lifecycle/common/lifecycle.js'; import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { OS } from '../../../../base/common/platform.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 31ff76a179e..c0526af39e8 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -18,7 +18,7 @@ import { IWorkingCopyService } from '../../workingCopy/common/workingCopyService import { IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup, NO_TYPE_ID, IWorkingCopySaveEvent } from '../../workingCopy/common/workingCopy.js'; import { IEncodingSupport, ILanguageSupport, ITextFileService } from '../../textfile/common/textfiles.js'; import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; import { ensureValidWordDefinition } from '../../../../editor/common/core/wordHelper.js'; import { IEditorService } from '../../editor/common/editorService.js'; @@ -349,7 +349,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } // Listen to text model events - const textEditorModel = assertIsDefined(this.textEditorModel); + const textEditorModel = assertReturnsDefined(this.textEditorModel); this.installModelListeners(textEditorModel); // Only adjust name and dirty state etc. if we diff --git a/src/vs/workbench/services/update/electron-sandbox/updateService.ts b/src/vs/workbench/services/update/electron-browser/updateService.ts similarity index 94% rename from src/vs/workbench/services/update/electron-sandbox/updateService.ts rename to src/vs/workbench/services/update/electron-browser/updateService.ts index 5ddd452efe4..c6bb0ae6063 100644 --- a/src/vs/workbench/services/update/electron-sandbox/updateService.ts +++ b/src/vs/workbench/services/update/electron-browser/updateService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IUpdateService } from '../../../../platform/update/common/update.js'; -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { UpdateChannelClient } from '../../../../platform/update/common/updateIpc.js'; registerMainProcessRemoteService(IUpdateService, 'update', { channelClientCtor: UpdateChannelClient }); diff --git a/src/vs/workbench/services/url/electron-sandbox/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts similarity index 100% rename from src/vs/workbench/services/url/electron-sandbox/urlService.ts rename to src/vs/workbench/services/url/electron-browser/urlService.ts diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts similarity index 98% rename from src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts rename to src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index 5c265c0ee65..69503a49c1e 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IUserDataAutoSyncService, SyncOptions, UserDataSyncError } from '../../../../platform/userDataSync/common/userDataSync.js'; -import { ISharedProcessService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { IChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { Event } from '../../../../base/common/event.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts similarity index 97% rename from src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts rename to src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index dc701548db8..a5b5857741f 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IUserDataSyncResourceProviderService, IUserDataSyncService, IUserDataSyncStoreManagementService } from '../../../../platform/userDataSync/common/userDataSync.js'; -import { registerSharedProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; +import { registerSharedProcessRemoteService } from '../../../../platform/ipc/electron-browser/services.js'; import { UserDataSyncServiceChannelClient } from '../../../../platform/userDataSync/common/userDataSyncServiceIpc.js'; import { IUserDataSyncMachinesService } from '../../../../platform/userDataSync/common/userDataSyncMachines.js'; import { UserDataSyncAccountServiceChannelClient, UserDataSyncStoreManagementServiceChannelClient } from '../../../../platform/userDataSync/common/userDataSyncIpc.js'; diff --git a/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts b/src/vs/workbench/services/utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.ts similarity index 99% rename from src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts rename to src/vs/workbench/services/utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.ts index 4f6672fda74..d074d17baeb 100644 --- a/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts +++ b/src/vs/workbench/services/utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.ts @@ -10,7 +10,7 @@ import { Client as MessagePortClient } from '../../../../base/parts/ipc/common/i import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IPCClient, ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { generateUuid } from '../../../../base/common/uuid.js'; -import { acquirePort } from '../../../../base/parts/ipc/electron-sandbox/ipc.mp.js'; +import { acquirePort } from '../../../../base/parts/ipc/electron-browser/ipc.mp.js'; import { IOnDidTerminateUtilityrocessWorkerProcess, ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerProcess, IUtilityProcessWorkerService } from '../../../../platform/utilityProcess/common/utilityProcessWorkerService.js'; import { Barrier, timeout } from '../../../../base/common/async.js'; diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index 97e0c77dd21..3565d55c3c9 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -11,7 +11,7 @@ import { workbenchInstantiationService } from '../../../../test/browser/workbenc import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; import { ViewDescriptorService } from '../../browser/viewDescriptorService.js'; -import { assertIsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; @@ -172,8 +172,8 @@ suite('ViewDescriptorService', () => { assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); - const generatedPanel = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[0].id)); - const generatedSidebar = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[2].id)); + const generatedPanel = assertReturnsDefined(testObject.getViewContainerByViewId(viewDescriptors[0].id)); + const generatedSidebar = assertReturnsDefined(testObject.getViewContainerByViewId(viewDescriptors[2].id)); assert.strictEqual(testObject.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); assert.strictEqual(testObject.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); @@ -311,8 +311,8 @@ suite('ViewDescriptorService', () => { testObject.moveViewsToContainer([viewDescriptors[1]], panelContainer); testObject.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - const generatedPanel = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[0].id)); - const generatedSidebar = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[2].id)); + const generatedPanel = assertReturnsDefined(testObject.getViewContainerByViewId(viewDescriptors[0].id)); + const generatedSidebar = assertReturnsDefined(testObject.getViewContainerByViewId(viewDescriptors[2].id)); testObject.reset(); diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index 39280946a7b..c12163d118e 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -13,7 +13,7 @@ import { IWorkingCopyService } from './workingCopyService.js'; import { IWorkingCopyBackup, IWorkingCopyBackupMeta, IWorkingCopySaveEvent, WorkingCopyCapabilities } from './workingCopy.js'; import { raceCancellation, TaskSequentializer, timeout } from '../../../../base/common/async.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { IWorkingCopyFileService } from './workingCopyFileService.js'; import { VSBufferReadableStream } from '../../../../base/common/buffer.js'; import { IFilesConfigurationService } from '../../filesConfiguration/common/filesConfigurationService.js'; @@ -1029,7 +1029,7 @@ export class StoredFileWorkingCopy extend // participant triggering progress.report({ message: localize('saveTextFile', "Writing into file...") }); this.trace(`doSave(${versionId}) - before write()`); - const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); + const lastResolvedFileStat = assertReturnsDefined(this.lastResolvedFileStat); const resolvedFileWorkingCopy = this; return this.saveSequentializer.run(versionId, (async () => { try { @@ -1074,9 +1074,9 @@ export class StoredFileWorkingCopy extend // Write them to disk if (options?.writeElevated && this.elevatedFileService.isSupported(lastResolvedFileStat.resource)) { - stat = await this.elevatedFileService.writeFileElevated(lastResolvedFileStat.resource, assertIsDefined(snapshot), writeFileOptions); + stat = await this.elevatedFileService.writeFileElevated(lastResolvedFileStat.resource, assertReturnsDefined(snapshot), writeFileOptions); } else { - stat = await this.fileService.writeFile(lastResolvedFileStat.resource, assertIsDefined(snapshot), writeFileOptions); + stat = await this.fileService.writeFile(lastResolvedFileStat.resource, assertReturnsDefined(snapshot), writeFileOptions); } } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts index 3b11c22384d..048f33d93e0 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts @@ -5,7 +5,7 @@ import { localize } from '../../../../nls.js'; import { Event, Emitter } from '../../../../base/common/event.js'; -import { assertIsDefined } from '../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../base/common/types.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; import { ILifecycleService, LifecyclePhase, WillShutdownEvent } from '../../lifecycle/common/lifecycle.js'; @@ -155,9 +155,9 @@ export class WorkingCopyHistoryModel { } private async doAddEntry(source: SaveSource, sourceDescription: string | undefined = undefined, timestamp: number, token: CancellationToken): Promise { - const workingCopyResource = assertIsDefined(this.workingCopyResource); - const workingCopyName = assertIsDefined(this.workingCopyName); - const historyEntriesFolder = assertIsDefined(this.historyEntriesFolder); + const workingCopyResource = assertReturnsDefined(this.workingCopyResource); + const workingCopyName = assertReturnsDefined(this.workingCopyName); + const historyEntriesFolder = assertReturnsDefined(this.historyEntriesFolder); // Perform a fast clone operation with minimal overhead to a new random location const id = `${randomPath(undefined, undefined, 4)}${extname(workingCopyResource)}`; @@ -185,7 +185,7 @@ export class WorkingCopyHistoryModel { } private async doReplaceEntry(entry: IWorkingCopyHistoryEntry, source: SaveSource, sourceDescription: string | undefined = undefined, timestamp: number, token: CancellationToken): Promise { - const workingCopyResource = assertIsDefined(this.workingCopyResource); + const workingCopyResource = assertReturnsDefined(this.workingCopyResource); // Perform a fast clone operation with minimal overhead to the existing location await this.fileService.cloneFile(workingCopyResource, entry.location); @@ -317,8 +317,8 @@ export class WorkingCopyHistoryModel { } private async resolveEntriesFromDisk(): Promise> { - const workingCopyResource = assertIsDefined(this.workingCopyResource); - const workingCopyName = assertIsDefined(this.workingCopyName); + const workingCopyResource = assertReturnsDefined(this.workingCopyResource); + const workingCopyName = assertReturnsDefined(this.workingCopyName); const [entryListing, entryStats] = await Promise.all([ @@ -364,13 +364,13 @@ export class WorkingCopyHistoryModel { async moveEntries(target: WorkingCopyHistoryModel, source: SaveSource, token: CancellationToken): Promise { const timestamp = Date.now(); - const sourceDescription = this.labelService.getUriLabel(assertIsDefined(this.workingCopyResource)); + const sourceDescription = this.labelService.getUriLabel(assertReturnsDefined(this.workingCopyResource)); // Move all entries into the target folder so that we preserve // any existing history entries that might already be present - const sourceHistoryEntriesFolder = assertIsDefined(this.historyEntriesFolder); - const targetHistoryEntriesFolder = assertIsDefined(target.historyEntriesFolder); + const sourceHistoryEntriesFolder = assertReturnsDefined(this.historyEntriesFolder); + const targetHistoryEntriesFolder = assertReturnsDefined(target.historyEntriesFolder); try { for (const entry of this.entries) { await this.fileService.move(entry.location, joinPath(targetHistoryEntriesFolder, entry.id), true); @@ -393,11 +393,11 @@ export class WorkingCopyHistoryModel { const allEntries = distinct([...this.entries, ...target.entries], entry => entry.id).sort((entryA, entryB) => entryA.timestamp - entryB.timestamp); // Update our associated working copy - const targetWorkingCopyResource = assertIsDefined(target.workingCopyResource); + const targetWorkingCopyResource = assertReturnsDefined(target.workingCopyResource); this.setWorkingCopy(targetWorkingCopyResource); // Restore our entries and ensure correct metadata - const targetWorkingCopyName = assertIsDefined(target.workingCopyName); + const targetWorkingCopyName = assertReturnsDefined(target.workingCopyName); for (const entry of allEntries) { this.entries.push({ id: entry.id, @@ -441,7 +441,7 @@ export class WorkingCopyHistoryModel { } private async doStore(token: CancellationToken): Promise { - const historyEntriesFolder = assertIsDefined(this.historyEntriesFolder); + const historyEntriesFolder = assertReturnsDefined(this.historyEntriesFolder); // Make sure to await resolving when persisting await this.resolveEntriesOnce(); @@ -505,8 +505,8 @@ export class WorkingCopyHistoryModel { } private async writeEntriesFile(): Promise { - const workingCopyResource = assertIsDefined(this.workingCopyResource); - const historyEntriesListingFile = assertIsDefined(this.historyEntriesListingFile); + const workingCopyResource = assertReturnsDefined(this.workingCopyResource); + const historyEntriesListingFile = assertReturnsDefined(this.historyEntriesListingFile); const serializedModel: ISerializedWorkingCopyHistoryModel = { version: 1, @@ -525,7 +525,7 @@ export class WorkingCopyHistoryModel { } private async readEntriesFile(): Promise { - const historyEntriesListingFile = assertIsDefined(this.historyEntriesListingFile); + const historyEntriesListingFile = assertReturnsDefined(this.historyEntriesListingFile); let serializedModel: ISerializedWorkingCopyHistoryModel | undefined = undefined; try { @@ -540,8 +540,8 @@ export class WorkingCopyHistoryModel { } private async readEntriesFolder(): Promise { - const historyEntriesFolder = assertIsDefined(this.historyEntriesFolder); - const historyEntriesNameMatcher = assertIsDefined(this.historyEntriesNameMatcher); + const historyEntriesFolder = assertReturnsDefined(this.historyEntriesFolder); + const historyEntriesNameMatcher = assertReturnsDefined(this.historyEntriesNameMatcher); let rawEntries: IFileStatWithMetadata[] | undefined = undefined; diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/electron-browser/workingCopyBackupService.ts similarity index 98% rename from src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts rename to src/vs/workbench/services/workingCopy/electron-browser/workingCopyBackupService.ts index fa5167d131e..ac4b7ec16ce 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/electron-browser/workingCopyBackupService.ts @@ -10,7 +10,7 @@ import { InstantiationType, registerSingleton } from '../../../../platform/insta import { IWorkingCopyBackupService } from '../common/workingCopyBackup.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { ILifecycleService } from '../../lifecycle/common/lifecycle.js'; import { NativeWorkingCopyBackupTracker } from './workingCopyBackupTracker.js'; diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-browser/workingCopyBackupTracker.ts similarity index 100% rename from src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts rename to src/vs/workbench/services/workingCopy/electron-browser/workingCopyBackupTracker.ts diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/electron-browser/workingCopyHistoryService.ts similarity index 100% rename from src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService.ts rename to src/vs/workbench/services/workingCopy/electron-browser/workingCopyHistoryService.ts diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts index 7d4a030d363..62c7cd79ea9 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts @@ -20,7 +20,7 @@ import { consumeReadable, consumeStream, isReadableStream } from '../../../../.. import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { SnapshotContext } from '../../common/fileWorkingCopy.js'; -import { assertIsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; export class TestStoredFileWorkingCopyModel extends Disposable implements IStoredFileWorkingCopyModel { @@ -518,7 +518,7 @@ suite('StoredFileWorkingCopy', function () { await workingCopy.resolve(); - const stat = assertIsDefined(getLastResolvedFileStat(workingCopy)); + const stat = assertReturnsDefined(getLastResolvedFileStat(workingCopy)); try { accessor.fileService.readShouldThrowError = new NotModifiedSinceFileOperationError('error', { ...stat, mtime: stat.mtime - 1, readonly: !stat.readonly, locked: !stat.locked }); await workingCopy.resolve(); diff --git a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts similarity index 99% rename from src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts index 41d4150230f..b3fb5c37566 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts @@ -15,10 +15,10 @@ import { createTextModel } from '../../../../../editor/test/common/testTextModel import { Schemas } from '../../../../../base/common/network.js'; import { FileService } from '../../../../../platform/files/common/fileService.js'; import { LogLevel, NullLogService } from '../../../../../platform/log/common/log.js'; -import { NativeWorkbenchEnvironmentService } from '../../../environment/electron-sandbox/environmentService.js'; +import { NativeWorkbenchEnvironmentService } from '../../../environment/electron-browser/environmentService.js'; import { toBufferOrReadable } from '../../../textfile/common/textfiles.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; -import { NativeWorkingCopyBackupService } from '../../electron-sandbox/workingCopyBackupService.js'; +import { NativeWorkingCopyBackupService } from '../../electron-browser/workingCopyBackupService.js'; import { FileUserDataProvider } from '../../../../../platform/userData/common/fileUserDataProvider.js'; import { bufferToReadable, bufferToStream, streamToBuffer, VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../../../base/common/buffer.js'; import { TestLifecycleService, toTypedWorkingCopyId, toUntypedWorkingCopyId } from '../../../../test/browser/workbenchTestServices.js'; diff --git a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts similarity index 99% rename from src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index ed1ce98a64c..a7ab03c1289 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -8,7 +8,7 @@ import { isMacintosh, isWindows } from '../../../../../base/common/platform.js'; import { join } from '../../../../../base/common/path.js'; import { URI } from '../../../../../base/common/uri.js'; import { hash } from '../../../../../base/common/hash.js'; -import { NativeWorkingCopyBackupTracker } from '../../electron-sandbox/workingCopyBackupTracker.js'; +import { NativeWorkingCopyBackupTracker } from '../../electron-browser/workingCopyBackupTracker.js'; import { TextFileEditorModelManager } from '../../../textfile/common/textFileEditorModelManager.js'; import { IEditorService } from '../../../editor/common/editorService.js'; import { EditorPart } from '../../../../browser/parts/editor/editorPart.js'; @@ -43,7 +43,7 @@ import { generateUuid } from '../../../../../base/common/uuid.js'; import { Schemas } from '../../../../../base/common/network.js'; import { joinPath } from '../../../../../base/common/resources.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; -import { TestServiceAccessor, workbenchInstantiationService } from '../../../../test/electron-sandbox/workbenchTestServices.js'; +import { TestServiceAccessor, workbenchInstantiationService } from '../../../../test/electron-browser/workbenchTestServices.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; suite('WorkingCopyBackupTracker (native)', function () { diff --git a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts similarity index 100% rename from src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryService.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts diff --git a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts similarity index 99% rename from src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryTracker.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts index 82f53dbd286..3bc44671452 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts @@ -23,7 +23,7 @@ import { TestDialogService } from '../../../../../platform/dialogs/test/common/t import { TestNotificationService } from '../../../../../platform/notification/test/common/testNotificationService.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { IWorkingCopyHistoryEntry, IWorkingCopyHistoryEntryDescriptor } from '../../common/workingCopyHistory.js'; -import { assertIsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; @@ -63,7 +63,7 @@ suite('WorkingCopyHistoryTracker', () => { timestamp: increasingTimestampCounter++ // very important to get tests to not be flaky with stable sort order }, token); - return assertIsDefined(entry); + return assertReturnsDefined(entry); } setup(async () => { diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts similarity index 99% rename from src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts rename to src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts index c285804f40a..cbdca7d06f9 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts @@ -17,7 +17,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { basename } from '../../../../base/common/resources.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { IFileService } from '../../../../platform/files/common/files.js'; -import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js'; import { ILifecycleService, ShutdownReason } from '../../lifecycle/common/lifecycle.js'; import { IFileDialogService, IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts similarity index 100% rename from src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts rename to src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts diff --git a/src/vs/workbench/test/browser/notificationsList.test.ts b/src/vs/workbench/test/browser/notificationsList.test.ts new file mode 100644 index 00000000000..c975c6533af --- /dev/null +++ b/src/vs/workbench/test/browser/notificationsList.test.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { NotificationAccessibilityProvider } from '../../browser/parts/notifications/notificationsList.js'; +import { NotificationViewItem, INotificationsFilter, INotificationViewItem } from '../../common/notifications.js'; +import { Severity, NotificationsFilter } from '../../../platform/notification/common/notification.js'; +import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js'; +import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; +import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js'; +import { MockKeybindingService } from '../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js'; + +suite('NotificationsList AccessibilityProvider', () => { + + const noFilter: INotificationsFilter = { global: NotificationsFilter.OFF, sources: new Map() }; + let configurationService: IConfigurationService; + let keybindingService: IKeybindingService; + let accessibilityProvider: NotificationAccessibilityProvider; + const createdNotifications: INotificationViewItem[] = []; + + setup(() => { + configurationService = new TestConfigurationService(); + keybindingService = new MockKeybindingService(); + accessibilityProvider = new NotificationAccessibilityProvider({}, keybindingService, configurationService); + }); + + teardown(() => { + // Close all created notifications to prevent disposable leaks + for (const notification of createdNotifications) { + notification.close(); + } + createdNotifications.length = 0; + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('getAriaLabel includes severity prefix for Error notifications', () => { + const notification = NotificationViewItem.create({ severity: Severity.Error, message: 'Something went wrong' }, noFilter)!; + createdNotifications.push(notification); + const ariaLabel = accessibilityProvider.getAriaLabel(notification); + + assert.ok(ariaLabel.startsWith('Error: '), `Expected aria label to start with "Error: ", but got: ${ariaLabel}`); + assert.ok(ariaLabel.includes('Something went wrong'), 'Expected aria label to include original message'); + assert.ok(ariaLabel.includes('notification'), 'Expected aria label to include "notification"'); + }); + + test('getAriaLabel includes severity prefix for Warning notifications', () => { + const notification = NotificationViewItem.create({ severity: Severity.Warning, message: 'This is a warning' }, noFilter)!; + createdNotifications.push(notification); + const ariaLabel = accessibilityProvider.getAriaLabel(notification); + + assert.ok(ariaLabel.startsWith('Warning: '), `Expected aria label to start with "Warning: ", but got: ${ariaLabel}`); + assert.ok(ariaLabel.includes('This is a warning'), 'Expected aria label to include original message'); + assert.ok(ariaLabel.includes('notification'), 'Expected aria label to include "notification"'); + }); + + test('getAriaLabel includes severity prefix for Info notifications', () => { + const notification = NotificationViewItem.create({ severity: Severity.Info, message: 'Information message' }, noFilter)!; + createdNotifications.push(notification); + const ariaLabel = accessibilityProvider.getAriaLabel(notification); + + assert.ok(ariaLabel.startsWith('Info: '), `Expected aria label to start with "Info: ", but got: ${ariaLabel}`); + assert.ok(ariaLabel.includes('Information message'), 'Expected aria label to include original message'); + assert.ok(ariaLabel.includes('notification'), 'Expected aria label to include "notification"'); + }); + + test('getAriaLabel includes source when present', () => { + const notification = NotificationViewItem.create({ + severity: Severity.Error, + message: 'Error with source', + source: 'TestExtension' + }, noFilter)!; + createdNotifications.push(notification); + const ariaLabel = accessibilityProvider.getAriaLabel(notification); + + assert.ok(ariaLabel.startsWith('Error: '), 'Expected aria label to start with severity prefix'); + assert.ok(ariaLabel.includes('Error with source'), 'Expected aria label to include original message'); + assert.ok(ariaLabel.includes('source: TestExtension'), 'Expected aria label to include source information'); + assert.ok(ariaLabel.includes('notification'), 'Expected aria label to include "notification"'); + }); + + test('severity prefix consistency', () => { + // Test that the severity prefixes are consistent with the ARIA alerts + const errorNotification = NotificationViewItem.create({ severity: Severity.Error, message: 'Error message' }, noFilter)!; + const warningNotification = NotificationViewItem.create({ severity: Severity.Warning, message: 'Warning message' }, noFilter)!; + const infoNotification = NotificationViewItem.create({ severity: Severity.Info, message: 'Info message' }, noFilter)!; + + createdNotifications.push(errorNotification, warningNotification, infoNotification); + + const errorLabel = accessibilityProvider.getAriaLabel(errorNotification); + const warningLabel = accessibilityProvider.getAriaLabel(warningNotification); + const infoLabel = accessibilityProvider.getAriaLabel(infoNotification); + + // Check that each severity type gets the correct prefix + assert.ok(errorLabel.includes('Error: Error message'), 'Error notifications should have Error prefix'); + assert.ok(warningLabel.includes('Warning: Warning message'), 'Warning notifications should have Warning prefix'); + assert.ok(infoLabel.includes('Info: Info message'), 'Info notifications should have Info prefix'); + }); +}); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 0eddf27ee9f..b9f1daec9db 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -123,7 +123,7 @@ import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreati import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from '../../../platform/workspace/common/workspaceTrust.js'; import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalBackend, ITerminalLogService, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from '../../../platform/terminal/common/terminal.js'; import { ICreateTerminalOptions, IDeserializedTerminalEditorInput, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from '../../contrib/terminal/browser/terminal.js'; -import { assertIsDefined, upcast } from '../../../base/common/types.js'; +import { assertReturnsDefined, upcast } from '../../../base/common/types.js'; import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService, type ITerminalConfiguration } from '../../contrib/terminal/common/terminal.js'; import { EditorResolverService } from '../../services/editor/browser/editorResolverService.js'; import { FILE_EDITOR_INPUT_ID } from '../../contrib/files/common/files.js'; @@ -739,7 +739,7 @@ export class TestPaneCompositeService extends Disposable implements IPaneComposi } getPartByLocation(viewContainerLocation: ViewContainerLocation): IPaneCompositePart { - return assertIsDefined(this.parts.get(viewContainerLocation)); + return assertReturnsDefined(this.parts.get(viewContainerLocation)); } } diff --git a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts b/src/vs/workbench/test/electron-browser/resolveExternal.test.ts similarity index 98% rename from src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts rename to src/vs/workbench/test/electron-browser/resolveExternal.test.ts index 530b5eb4ef2..94f7576fbdd 100644 --- a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts +++ b/src/vs/workbench/test/electron-browser/resolveExternal.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js'; -import { NativeWindow } from '../../electron-sandbox/window.js'; +import { NativeWindow } from '../../electron-browser/window.js'; import { ITunnelService, RemoteTunnel } from '../../../platform/tunnel/common/tunnel.js'; import { URI } from '../../../base/common/uri.js'; import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js'; diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts similarity index 99% rename from src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts rename to src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 83c50d25e72..84d5af6fe3f 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -5,7 +5,7 @@ import { Event } from '../../../base/common/event.js'; import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestEncodingOracle, TestEnvironmentService, TestFileDialogService, TestFilesConfigurationService, TestFileService, TestLifecycleService, TestTextFileService } from '../browser/workbenchTestServices.js'; -import { ISharedProcessService } from '../../../platform/ipc/electron-sandbox/services.js'; +import { ISharedProcessService } from '../../../platform/ipc/electron-browser/services.js'; import { INativeHostService, INativeHostOptions, IOSProperties, IOSStatistics } from '../../../platform/native/common/native.js'; import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../base/common/buffer.js'; import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; @@ -36,7 +36,7 @@ import { ILifecycleService } from '../../services/lifecycle/common/lifecycle.js' import { IWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackup.js'; import { IWorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; import { TestContextService } from '../common/workbenchTestServices.js'; -import { NativeTextFileService } from '../../services/textfile/electron-sandbox/nativeTextFileService.js'; +import { NativeTextFileService } from '../../services/textfile/electron-browser/nativeTextFileService.js'; import { insert } from '../../../base/common/arrays.js'; import { Schemas } from '../../../base/common/network.js'; import { FileService } from '../../../platform/files/common/fileService.js'; @@ -44,7 +44,7 @@ import { InMemoryFileSystemProvider } from '../../../platform/files/common/inMem import { NullLogService } from '../../../platform/log/common/log.js'; import { FileUserDataProvider } from '../../../platform/userData/common/fileUserDataProvider.js'; import { IWorkingCopyIdentifier } from '../../services/workingCopy/common/workingCopy.js'; -import { NativeWorkingCopyBackupService } from '../../services/workingCopy/electron-sandbox/workingCopyBackupService.js'; +import { NativeWorkingCopyBackupService } from '../../services/workingCopy/electron-browser/workingCopyBackupService.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { UriIdentityService } from '../../../platform/uriIdentity/common/uriIdentityService.js'; import { UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index b05c17aac15..c9df215ea42 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -19,75 +19,75 @@ import './workbench.common.main.js'; //#region --- workbench (desktop main) -import './electron-sandbox/desktop.main.js'; -import './electron-sandbox/desktop.contribution.js'; +import './electron-browser/desktop.main.js'; +import './electron-browser/desktop.contribution.js'; //#endregion //#region --- workbench parts -import './electron-sandbox/parts/dialogs/dialog.contribution.js'; +import './electron-browser/parts/dialogs/dialog.contribution.js'; //#endregion //#region --- workbench services -import './services/textfile/electron-sandbox/nativeTextFileService.js'; -import './services/dialogs/electron-sandbox/fileDialogService.js'; -import './services/workspaces/electron-sandbox/workspacesService.js'; -import './services/menubar/electron-sandbox/menubarService.js'; -import './services/update/electron-sandbox/updateService.js'; -import './services/url/electron-sandbox/urlService.js'; -import './services/lifecycle/electron-sandbox/lifecycleService.js'; -import './services/title/electron-sandbox/titleService.js'; -import './services/host/electron-sandbox/nativeHostService.js'; -import './services/request/electron-sandbox/requestService.js'; -import './services/clipboard/electron-sandbox/clipboardService.js'; -import './services/contextmenu/electron-sandbox/contextmenuService.js'; -import './services/workspaces/electron-sandbox/workspaceEditingService.js'; -import './services/configurationResolver/electron-sandbox/configurationResolverService.js'; -import './services/accessibility/electron-sandbox/accessibilityService.js'; -import './services/keybinding/electron-sandbox/nativeKeyboardLayout.js'; -import './services/path/electron-sandbox/pathService.js'; -import './services/themes/electron-sandbox/nativeHostColorSchemeService.js'; -import './services/extensionManagement/electron-sandbox/extensionManagementService.js'; -import './services/encryption/electron-sandbox/encryptionService.js'; -import './services/browserElements/electron-sandbox/browserElementsService.js'; -import './services/secrets/electron-sandbox/secretStorageService.js'; -import './services/localization/electron-sandbox/languagePackService.js'; -import './services/telemetry/electron-sandbox/telemetryService.js'; -import './services/extensions/electron-sandbox/extensionHostStarter.js'; +import './services/textfile/electron-browser/nativeTextFileService.js'; +import './services/dialogs/electron-browser/fileDialogService.js'; +import './services/workspaces/electron-browser/workspacesService.js'; +import './services/menubar/electron-browser/menubarService.js'; +import './services/update/electron-browser/updateService.js'; +import './services/url/electron-browser/urlService.js'; +import './services/lifecycle/electron-browser/lifecycleService.js'; +import './services/title/electron-browser/titleService.js'; +import './services/host/electron-browser/nativeHostService.js'; +import './services/request/electron-browser/requestService.js'; +import './services/clipboard/electron-browser/clipboardService.js'; +import './services/contextmenu/electron-browser/contextmenuService.js'; +import './services/workspaces/electron-browser/workspaceEditingService.js'; +import './services/configurationResolver/electron-browser/configurationResolverService.js'; +import './services/accessibility/electron-browser/accessibilityService.js'; +import './services/keybinding/electron-browser/nativeKeyboardLayout.js'; +import './services/path/electron-browser/pathService.js'; +import './services/themes/electron-browser/nativeHostColorSchemeService.js'; +import './services/extensionManagement/electron-browser/extensionManagementService.js'; +import './services/encryption/electron-browser/encryptionService.js'; +import './services/browserElements/electron-browser/browserElementsService.js'; +import './services/secrets/electron-browser/secretStorageService.js'; +import './services/localization/electron-browser/languagePackService.js'; +import './services/telemetry/electron-browser/telemetryService.js'; +import './services/extensions/electron-browser/extensionHostStarter.js'; import '../platform/extensionResourceLoader/common/extensionResourceLoaderService.js'; -import './services/localization/electron-sandbox/localeService.js'; -import './services/extensions/electron-sandbox/extensionsScannerService.js'; -import './services/extensionManagement/electron-sandbox/extensionManagementServerService.js'; -import './services/extensionManagement/electron-sandbox/extensionGalleryManifestService.js'; -import './services/extensionManagement/electron-sandbox/extensionTipsService.js'; -import './services/userDataSync/electron-sandbox/userDataSyncService.js'; -import './services/userDataSync/electron-sandbox/userDataAutoSyncService.js'; -import './services/timer/electron-sandbox/timerService.js'; -import './services/environment/electron-sandbox/shellEnvironmentService.js'; -import './services/integrity/electron-sandbox/integrityService.js'; -import './services/workingCopy/electron-sandbox/workingCopyBackupService.js'; -import './services/checksum/electron-sandbox/checksumService.js'; -import '../platform/remote/electron-sandbox/sharedProcessTunnelService.js'; -import './services/tunnel/electron-sandbox/tunnelService.js'; -import '../platform/diagnostics/electron-sandbox/diagnosticsService.js'; -import '../platform/profiling/electron-sandbox/profilingService.js'; -import '../platform/telemetry/electron-sandbox/customEndpointTelemetryService.js'; -import '../platform/remoteTunnel/electron-sandbox/remoteTunnelService.js'; -import './services/files/electron-sandbox/elevatedFileService.js'; -import './services/search/electron-sandbox/searchService.js'; -import './services/workingCopy/electron-sandbox/workingCopyHistoryService.js'; +import './services/localization/electron-browser/localeService.js'; +import './services/extensions/electron-browser/extensionsScannerService.js'; +import './services/extensionManagement/electron-browser/extensionManagementServerService.js'; +import './services/extensionManagement/electron-browser/extensionGalleryManifestService.js'; +import './services/extensionManagement/electron-browser/extensionTipsService.js'; +import './services/userDataSync/electron-browser/userDataSyncService.js'; +import './services/userDataSync/electron-browser/userDataAutoSyncService.js'; +import './services/timer/electron-browser/timerService.js'; +import './services/environment/electron-browser/shellEnvironmentService.js'; +import './services/integrity/electron-browser/integrityService.js'; +import './services/workingCopy/electron-browser/workingCopyBackupService.js'; +import './services/checksum/electron-browser/checksumService.js'; +import '../platform/remote/electron-browser/sharedProcessTunnelService.js'; +import './services/tunnel/electron-browser/tunnelService.js'; +import '../platform/diagnostics/electron-browser/diagnosticsService.js'; +import '../platform/profiling/electron-browser/profilingService.js'; +import '../platform/telemetry/electron-browser/customEndpointTelemetryService.js'; +import '../platform/remoteTunnel/electron-browser/remoteTunnelService.js'; +import './services/files/electron-browser/elevatedFileService.js'; +import './services/search/electron-browser/searchService.js'; +import './services/workingCopy/electron-browser/workingCopyHistoryService.js'; import './services/userDataSync/browser/userDataSyncEnablementService.js'; -import './services/extensions/electron-sandbox/nativeExtensionService.js'; -import '../platform/userDataProfile/electron-sandbox/userDataProfileStorageService.js'; -import './services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.js'; -import '../platform/extensionManagement/electron-sandbox/extensionsProfileScannerService.js'; -import '../platform/webContentExtractor/electron-sandbox/webContentExtractorService.js'; -import './services/process/electron-sandbox/processService.js'; +import './services/extensions/electron-browser/nativeExtensionService.js'; +import '../platform/userDataProfile/electron-browser/userDataProfileStorageService.js'; +import './services/auxiliaryWindow/electron-browser/auxiliaryWindowService.js'; +import '../platform/extensionManagement/electron-browser/extensionsProfileScannerService.js'; +import '../platform/webContentExtractor/electron-browser/webContentExtractorService.js'; +import './services/process/electron-browser/processService.js'; import { registerSingleton } from '../platform/instantiation/common/extensions.js'; import { IUserDataInitializationService, UserDataInitializationService } from './services/userData/browser/userDataInit.js'; @@ -102,84 +102,84 @@ registerSingleton(IUserDataInitializationService, new SyncDescriptor(UserDataIni //#region --- workbench contributions // Logs -import './contrib/logs/electron-sandbox/logs.contribution.js'; +import './contrib/logs/electron-browser/logs.contribution.js'; // Localizations -import './contrib/localization/electron-sandbox/localization.contribution.js'; +import './contrib/localization/electron-browser/localization.contribution.js'; // Explorer -import './contrib/files/electron-sandbox/fileActions.contribution.js'; +import './contrib/files/electron-browser/fileActions.contribution.js'; // CodeEditor Contributions -import './contrib/codeEditor/electron-sandbox/codeEditor.contribution.js'; +import './contrib/codeEditor/electron-browser/codeEditor.contribution.js'; // Debug -import './contrib/debug/electron-sandbox/extensionHostDebugService.js'; +import './contrib/debug/electron-browser/extensionHostDebugService.js'; // Extensions Management -import './contrib/extensions/electron-sandbox/extensions.contribution.js'; +import './contrib/extensions/electron-browser/extensions.contribution.js'; // Issues -import './contrib/issue/electron-sandbox/issue.contribution.js'; +import './contrib/issue/electron-browser/issue.contribution.js'; // Process Explorer -import './contrib/processExplorer/electron-sandbox/processExplorer.contribution.js'; +import './contrib/processExplorer/electron-browser/processExplorer.contribution.js'; // Remote -import './contrib/remote/electron-sandbox/remote.contribution.js'; +import './contrib/remote/electron-browser/remote.contribution.js'; // Terminal -import './contrib/terminal/electron-sandbox/terminal.contribution.js'; +import './contrib/terminal/electron-browser/terminal.contribution.js'; // Themes import './contrib/themes/browser/themes.test.contribution.js'; -import './services/themes/electron-sandbox/themes.contribution.js'; +import './services/themes/electron-browser/themes.contribution.js'; // User Data Sync -import './contrib/userDataSync/electron-sandbox/userDataSync.contribution.js'; +import './contrib/userDataSync/electron-browser/userDataSync.contribution.js'; // Tags -import './contrib/tags/electron-sandbox/workspaceTagsService.js'; -import './contrib/tags/electron-sandbox/tags.contribution.js'; +import './contrib/tags/electron-browser/workspaceTagsService.js'; +import './contrib/tags/electron-browser/tags.contribution.js'; // Performance -import './contrib/performance/electron-sandbox/performance.contribution.js'; +import './contrib/performance/electron-browser/performance.contribution.js'; // Tasks -import './contrib/tasks/electron-sandbox/taskService.js'; +import './contrib/tasks/electron-browser/taskService.js'; // External terminal -import './contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.js'; +import './contrib/externalTerminal/electron-browser/externalTerminal.contribution.js'; // Webview -import './contrib/webview/electron-sandbox/webview.contribution.js'; +import './contrib/webview/electron-browser/webview.contribution.js'; // Splash -import './contrib/splash/electron-sandbox/splash.contribution.js'; +import './contrib/splash/electron-browser/splash.contribution.js'; // Local History -import './contrib/localHistory/electron-sandbox/localHistory.contribution.js'; +import './contrib/localHistory/electron-browser/localHistory.contribution.js'; // Merge Editor -import './contrib/mergeEditor/electron-sandbox/mergeEditor.contribution.js'; +import './contrib/mergeEditor/electron-browser/mergeEditor.contribution.js'; // Multi Diff Editor import './contrib/multiDiffEditor/browser/multiDiffEditor.contribution.js'; // Remote Tunnel -import './contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.js'; +import './contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.js'; // Chat -import './contrib/chat/electron-sandbox/chat.contribution.js'; -import './contrib/inlineChat/electron-sandbox/inlineChat.contribution.js'; +import './contrib/chat/electron-browser/chat.contribution.js'; +import './contrib/inlineChat/electron-browser/inlineChat.contribution.js'; // Encryption -import './contrib/encryption/electron-sandbox/encryption.contribution.js'; +import './contrib/encryption/electron-browser/encryption.contribution.js'; // Emergency Alert -import './contrib/emergencyAlert/electron-sandbox/emergencyAlert.contribution.js'; +import './contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.js'; // MCP -import './contrib/mcp/electron-sandbox/mcp.contribution.js'; +import './contrib/mcp/electron-browser/mcp.contribution.js'; //#endregion -export { main } from './electron-sandbox/desktop.main.js'; +export { main } from './electron-browser/desktop.main.js'; diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 59d832a1faa..3dfb05984ae 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,10 +5,6 @@ declare module 'vscode' { - export interface ChatResponseFragment2 { - index: number; - part: LanguageModelTextPart | LanguageModelToolCallPart; - } // @API extension ship a d.ts files for their options @@ -16,19 +12,141 @@ declare module 'vscode' { // concrete models. The `provideLanguageModelChatData` would do the discovery and auth dances and later // the model data is passed to the concrete function for making a requested or counting token - export interface LanguageModelChatData { - // like ChatResponseProviderMetadata + + // TODO@API name scheme + export interface LanguageModelChatRequestHandleOptions { + + // initiator + readonly extensionId: string; + + /** + * A set of options that control the behavior of the language model. These options are specific to the language model + * and need to be looked up in the respective documentation. + */ + readonly modelOptions: { [name: string]: any }; + + /** + * An optional list of tools that are available to the language model. These could be registered tools available via + * {@link lm.tools}, or private tools that are just implemented within the calling extension. + * + * If the LLM requests to call one of these tools, it will return a {@link LanguageModelToolCallPart} in + * {@link LanguageModelChatResponse.stream}. It's the caller's responsibility to invoke the tool. If it's a tool + * registered in {@link lm.tools}, that means calling {@link lm.invokeTool}. + * + * Then, the tool result can be provided to the LLM by creating an Assistant-type {@link LanguageModelChatMessage} with a + * {@link LanguageModelToolCallPart}, followed by a User-type message with a {@link LanguageModelToolResultPart}. + */ + tools?: LanguageModelChatTool[]; + + /** + * The tool-selecting mode to use. {@link LanguageModelChatToolMode.Auto} by default. + */ + toolMode?: LanguageModelChatToolMode; } - export interface LanguageModelChatProvider2 { + // TODO@API names: LanguageModelChatMetadata, LanguageModelChatItem + export interface LanguageModelChatInformation { - provideLanguageModelChatData(options: { force: boolean }, token: CancellationToken): ProviderResult; + // TODO@API IMPLICT from package-json registration + // readonly vendor: string; - provideResponse(model: LanguageModelChatData, messages: Array, options: LanguageModelChatRequestOptions, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + readonly id: string; - provideTokenCount(model: LanguageModelChatData, text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token: CancellationToken): Thenable; + /** + * Human-readable name of the language model. + */ + readonly name: string; + /** + * Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama` + * but they are defined by extensions contributing languages and subject to change. + */ + readonly family: string; + + /** + * An optional, human-readable description of the language model. + */ + readonly description?: string; + + /** + * An optional, human-readable string representing the cost of using the language model. + */ + readonly cost?: string; + + /** + * Opaque version string of the model. This is defined by the extension contributing the language model + * and subject to change while the identifier is stable. + */ + readonly version: string; + + readonly maxInputTokens: number; + + readonly maxOutputTokens: number; + + /** + * When present, this gates the use of `requestLanguageModelAccess` behind an authorization flow where + * the user must approve of another extension accessing the models contributed by this extension. + * Additionally, the extension can provide a label that will be shown in the UI. + */ + auth?: true | { label: string }; + + // TODO@API maybe an enum, LanguageModelChatProviderPickerAvailability? + // TODO@API isPreselected proposed + readonly isDefault?: boolean; + + // TODO@API nuke + readonly isUserSelectable?: boolean; + + readonly capabilities?: { + + // TODO@API have mimeTypes that you support + readonly vision?: boolean; + + // TODO@API should be `boolean | number` so extensions can express how many tools they support + readonly toolCalling?: boolean | number; + + // TODO@API DO NOT SUPPORT THIS + // readonly agentMode?: boolean; + + // TODO@API support prompt TSX style messages, MAYBE leave it out for now + readonly promptTsx?: boolean; + }; + + /** + * Optional category to group models by in the model picker. + * The lower the order, the higher the category appears in the list. + * Has no effect if `isUserSelectable` is `false`. + * If not specified, the model will appear in the "Other Models" category. + */ + readonly category?: { label: string; order: number }; } + export interface LanguageModelChatProvider2 { + + // signals a change from the provider to the editor so that prepareLanguageModelChat is called again + onDidChange?: Event; + + // NOT cacheable (between reloads) + prepareLanguageModelChat(options: { silent: boolean }, token: CancellationToken): ProviderResult; + + provideLanguageModelChatResponse(model: T, messages: Array, options: LanguageModelChatRequestHandleOptions, progress: Progress, token: CancellationToken): Thenable; + + provideTokenCount(model: T, text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token: CancellationToken): Thenable; + } + + export namespace lm { + + // + // export function registerChatModelProvider(vendor: string, provider: LanguageModelChatProvider2): Disposable; + } + + + + export interface ChatResponseFragment2 { + index: number; + part: LanguageModelTextPart | LanguageModelToolCallPart; + } + + /** * Represents a large language model that accepts ChatML messages and produces a streaming response */ @@ -37,6 +155,8 @@ declare module 'vscode' { // TODO@API remove or keep proposed? onDidReceiveLanguageModelResponse2?: Event<{ readonly extensionId: string; readonly participant?: string; readonly tokenCount?: number }>; + // TODO@API + // have dedicated options, don't reuse the LanguageModelChatRequestOptions so that consumer and provider part of the API can develop independently provideLanguageModelResponse(messages: Array, options: LanguageModelChatRequestOptions, extensionId: string, progress: Progress, token: CancellationToken): Thenable; provideTokenCount(text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token: CancellationToken): Thenable; @@ -88,10 +208,18 @@ declare module 'vscode' { // TODO@API maybe an enum, LanguageModelChatProviderPickerAvailability? readonly isDefault?: boolean; readonly isUserSelectable?: boolean; + readonly capabilities?: { readonly vision?: boolean; + + // TODO@API should be `boolean | number` so extensions can express how many tools they support readonly toolCalling?: boolean; + + // TODO@API WHY is agentMode a capability? This seems wrong? readonly agentMode?: boolean; + + // TODO@API support prompt TSX style messages + // readonly promptTsx?:boolean }; /** @@ -105,6 +233,7 @@ declare module 'vscode' { export interface ChatResponseProviderMetadata { // limit this provider to some extensions + // TODO@API remove? unused? extensions?: string[]; } diff --git a/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts b/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts index 902ed64ec97..d2cd676400a 100644 --- a/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts @@ -68,6 +68,7 @@ declare module 'vscode' { * @param data Binary image data * @param mimeType The MIME type of the image */ + // TODO@API just use string, no enum required static image(data: Uint8Array, mimeType: ChatImageMimeType): LanguageModelDataPart; static json(value: any, mime?: string): LanguageModelDataPart; diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 49c3fa397af..93735593d55 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -66,6 +66,8 @@ declare module 'vscode' { Option = 5, OptionValue = 6, Flag = 7, + SymbolicLinkFile = 8, + SymbolicLinkFolder = 9, } export interface TerminalCompletionContext { diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index ae612626c9e..d3c697873a1 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -30,6 +30,7 @@ export interface LaunchOptions { readonly headless?: boolean; readonly browser?: 'chromium' | 'webkit' | 'firefox' | 'chromium-msedge' | 'chromium-chrome'; readonly quality: Quality; + version: { major: number; minor: number; patch: number }; } interface ICodeInstance { @@ -89,7 +90,7 @@ export async function launch(options: LaunchOptions): Promise { const { serverProcess, driver } = await measureAndLog(() => launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger); registerInstance(serverProcess, options.logger, 'server'); - return new Code(driver, options.logger, serverProcess, undefined, options.quality); + return new Code(driver, options.logger, serverProcess, undefined, options.quality, options.version); } // Electron smoke tests (playwright) @@ -97,7 +98,7 @@ export async function launch(options: LaunchOptions): Promise { const { electronProcess, driver } = await measureAndLog(() => launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger); const { safeToKill } = registerInstance(electronProcess, options.logger, 'electron'); - return new Code(driver, options.logger, electronProcess, safeToKill, options.quality); + return new Code(driver, options.logger, electronProcess, safeToKill, options.quality, options.version); } } @@ -110,7 +111,8 @@ export class Code { readonly logger: Logger, private readonly mainProcess: cp.ChildProcess, private readonly safeToKill: Promise | undefined, - readonly quality: Quality + readonly quality: Quality, + readonly version: { major: number; minor: number; patch: number } ) { this.driver = new Proxy(driver, { get(target, prop) { @@ -131,6 +133,10 @@ export class Code { }); } + get editContextEnabled(): boolean { + return !(this.quality === Quality.Stable && this.version.major === 1 && this.version.minor < 101); + } + async startTracing(name: string): Promise { return await this.driver.startTracing(name); } diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts index 43f974ebec9..2b8623bb013 100644 --- a/test/automation/src/debug.ts +++ b/test/automation/src/debug.ts @@ -9,7 +9,6 @@ import { Code, findElement } from './code'; import { Editors } from './editors'; import { Editor } from './editor'; import { IElement } from './driver'; -import { Quality } from './application'; const VIEWLET = 'div[id="workbench.view.debug"]'; const DEBUG_VIEW = `${VIEWLET}`; @@ -133,7 +132,7 @@ export class Debug extends Viewlet { async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise { await this.commands.runCommand('Debug: Focus on Debug Console View'); - const selector = this.code.quality === Quality.Stable ? REPL_FOCUSED_TEXTAREA : REPL_FOCUSED_NATIVE_EDIT_CONTEXT; + const selector = !this.code.editContextEnabled ? REPL_FOCUSED_TEXTAREA : REPL_FOCUSED_NATIVE_EDIT_CONTEXT; await this.code.waitForActiveElement(selector); await this.code.waitForSetValue(selector, text); diff --git a/test/automation/src/editor.ts b/test/automation/src/editor.ts index 6587b28edaf..37e152d3e66 100644 --- a/test/automation/src/editor.ts +++ b/test/automation/src/editor.ts @@ -6,7 +6,6 @@ import { References } from './peek'; import { Commands } from './workbench'; import { Code } from './code'; -import { Quality } from './application'; const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box'; const RENAME_INPUT = `${RENAME_BOX} .rename-input`; @@ -107,7 +106,7 @@ export class Editor { } private _editContextSelector() { - return this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'; + return !this.code.editContextEnabled ? 'textarea' : '.native-edit-context'; } async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise { diff --git a/test/automation/src/editors.ts b/test/automation/src/editors.ts index b2321e947de..0f2e54722d0 100644 --- a/test/automation/src/editors.ts +++ b/test/automation/src/editors.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Quality } from './application'; import { Code } from './code'; export class Editors { @@ -53,7 +52,7 @@ export class Editors { } async waitForActiveEditor(fileName: string, retryCount?: number): Promise { - const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`; + const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] ${!this.code.editContextEnabled ? 'textarea' : '.native-edit-context'}`; return this.code.waitForActiveElement(selector, retryCount); } diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index 06bd324465f..8c40cdff0de 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -8,7 +8,6 @@ import { Code } from './code'; import { ncp } from 'ncp'; import { promisify } from 'util'; import { Commands } from './workbench'; -import { Quality } from './application'; import path = require('path'); import fs = require('fs'); @@ -21,7 +20,7 @@ export class Extensions extends Viewlet { async searchForExtension(id: string): Promise { await this.commands.runCommand('Extensions: Focus on Extensions View', { exactLabelMatch: true }); - await this.code.waitForTypeInEditor(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`, `@id:${id}`); + await this.code.waitForTypeInEditor(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor ${!this.code.editContextEnabled ? 'textarea' : '.native-edit-context'}`, `@id:${id}`); await this.code.waitForTextContent(`div.part.sidebar div.composite.title h2`, 'Extensions: Marketplace'); let retrials = 1; diff --git a/test/automation/src/notebook.ts b/test/automation/src/notebook.ts index 3ba9101a1de..7596d1888a8 100644 --- a/test/automation/src/notebook.ts +++ b/test/automation/src/notebook.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Quality } from './application'; import { Code } from './code'; import { QuickAccess } from './quickaccess'; import { QuickInput } from './quickinput'; @@ -47,7 +46,7 @@ export class Notebook { await this.code.waitForElement(editor); - const editContext = `${editor} ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`; + const editContext = `${editor} ${!this.code.editContextEnabled ? 'textarea' : '.native-edit-context'}`; await this.code.waitForActiveElement(editContext); await this.code.waitForTypeInEditor(editContext, text); diff --git a/test/automation/src/scm.ts b/test/automation/src/scm.ts index 2277bcb2875..0fb54bd15d2 100644 --- a/test/automation/src/scm.ts +++ b/test/automation/src/scm.ts @@ -6,7 +6,6 @@ import { Viewlet } from './viewlet'; import { IElement } from './driver'; import { findElement, findElements, Code } from './code'; -import { Quality } from './application'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT_NATIVE_EDIT_CONTEXT = `${VIEWLET} .scm-editor .native-edit-context`; @@ -79,6 +78,6 @@ export class SCM extends Viewlet { } private _editContextSelector(): string { - return this.code.quality === Quality.Stable ? SCM_INPUT_TEXTAREA : SCM_INPUT_NATIVE_EDIT_CONTEXT; + return !this.code.editContextEnabled ? SCM_INPUT_TEXTAREA : SCM_INPUT_NATIVE_EDIT_CONTEXT; } } diff --git a/test/automation/src/settings.ts b/test/automation/src/settings.ts index f4a8afb3165..ab961478b37 100644 --- a/test/automation/src/settings.ts +++ b/test/automation/src/settings.ts @@ -7,7 +7,6 @@ import { Editor } from './editor'; import { Editors } from './editors'; import { Code } from './code'; import { QuickAccess } from './quickaccess'; -import { Quality } from './application'; const SEARCH_BOX_NATIVE_EDIT_CONTEXT = '.settings-editor .suggest-input-container .monaco-editor .native-edit-context'; const SEARCH_BOX_TEXTAREA = '.settings-editor .suggest-input-container .monaco-editor textarea'; @@ -26,7 +25,7 @@ export class SettingsEditor { await this.editors.selectTab('settings.json'); await this.code.sendKeybinding('right', () => - this.editor.waitForEditorSelection('settings.json', (s) => this._acceptEditorSelection(this.code.quality, s))); + this.editor.waitForEditorSelection('settings.json', (s) => this._acceptEditorSelection(this.code.editContextEnabled, s))); await this.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value},`); await this.editors.saveOpenedFile(); } @@ -42,7 +41,7 @@ export class SettingsEditor { await this.editors.selectTab('settings.json'); await this.code.sendKeybinding('right', () => - this.editor.waitForEditorSelection('settings.json', (s) => this._acceptEditorSelection(this.code.quality, s))); + this.editor.waitForEditorSelection('settings.json', (s) => this._acceptEditorSelection(this.code.editContextEnabled, s))); await this.editor.waitForTypeInEditor('settings.json', settings.map(v => `"${v[0]}": ${v[1]},`).join('')); await this.editors.saveOpenedFile(); } @@ -85,11 +84,11 @@ export class SettingsEditor { } private _editContextSelector() { - return this.code.quality === Quality.Stable ? SEARCH_BOX_TEXTAREA : SEARCH_BOX_NATIVE_EDIT_CONTEXT; + return !this.code.editContextEnabled ? SEARCH_BOX_TEXTAREA : SEARCH_BOX_NATIVE_EDIT_CONTEXT; } - private _acceptEditorSelection(quality: Quality, s: { selectionStart: number; selectionEnd: number }): boolean { - if (quality === Quality.Stable) { + private _acceptEditorSelection(editContextEnabled: boolean, s: { selectionStart: number; selectionEnd: number }): boolean { + if (!editContextEnabled) { return true; } return s.selectionStart === 1 && s.selectionEnd === 1; diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 9788813f225..123076e130e 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -7,7 +7,7 @@ import { join } from 'path'; import { Application, ApplicationOptions, Logger, Quality } from '../../../../automation'; import { createApp, timeout, installDiagnosticsHandler, installAppAfterHandler, getRandomUserDataDir, suiteLogsPath, suiteCrashPath } from '../../utils'; -export function setup(ensureStableCode: () => string | undefined, logger: Logger) { +export function setup(ensureStableCode: () => { stableCodePath: string | undefined; stableCodeVersion: { major: number; minor: number; patch: number } | undefined }, logger: Logger) { describe('Data Loss (insiders -> insiders)', function () { // Double the timeout since these tests involve 2 startups @@ -146,7 +146,7 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger installAppAfterHandler(() => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { - const stableCodePath = ensureStableCode(); + const { stableCodePath, stableCodeVersion } = ensureStableCode(); if (!stableCodePath) { this.skip(); } @@ -170,6 +170,7 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger stableOptions.quality = Quality.Stable; stableOptions.logsPath = logsPath; stableOptions.crashesPath = crashesPath; + stableOptions.version = stableCodeVersion ?? { major: 0, minor: 0, patch: 0 }; stableApp = new Application(stableOptions); await stableApp.start(); @@ -210,7 +211,7 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger }); async function testHotExit(this: import('mocha').Context, title: string, restartDelay: number | undefined) { - const stableCodePath = ensureStableCode(); + const { stableCodePath, stableCodeVersion } = ensureStableCode(); if (!stableCodePath) { this.skip(); } @@ -225,6 +226,7 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger stableOptions.quality = Quality.Stable; stableOptions.logsPath = logsPath; stableOptions.crashesPath = crashesPath; + stableOptions.version = stableCodeVersion ?? { major: 0, minor: 0, patch: 0 }; stableApp = new Application(stableOptions); await stableApp.start(); diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index d8120ea0675..bdc5a3776e2 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -322,6 +322,8 @@ async function ensureStableCode(): Promise { // VSCode/Code.exe (Windows) | VSCode/code (Linux) stableCodePath = path.dirname(stableCodeExecutable); } + + opts['stable-version'] = parseVersion(stableVersion); } if (!fs.existsSync(stableCodePath)) { @@ -352,6 +354,7 @@ before(async function () { this.defaultOptions = { quality, + version: parseVersion(version ?? '0.0.0'), codePath: opts.build, workspacePath, userDataDir, @@ -396,7 +399,7 @@ after(async function () { }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataLossTests(() => opts['stable-build'] /* Do not change, deferred for a reason! */, logger); } + if (!opts.web) { setupDataLossTests(() => { return { stableCodePath: opts['stable-build'], stableCodeVersion: opts['stable-version'] } /* Do not change, deferred for a reason! */; }, logger); } setupPreferencesTests(logger); setupSearchTests(logger); if (!opts.web) { setupNotebookTests(logger); } diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index e5cf68ccab5..d112d64ea80 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -114,7 +114,7 @@ function ensureIsArray(a) { const testModules = (async function () { - const excludeGlob = '**/{node,electron-sandbox,electron-main,electron-utility}/**/*.test.js'; + const excludeGlob = '**/{node,electron-browser,electron-main,electron-utility}/**/*.test.js'; let isDefaultModules = true; let promise; diff --git a/test/unit/node/index.js b/test/unit/node/index.js index 36c2164dd98..4dc32d7a0cf 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -57,7 +57,7 @@ Options: const TEST_GLOB = '**/test/**/*.test.js'; const excludeGlobs = [ - '**/{browser,electron-sandbox,electron-main,electron-utility}/**/*.test.js', + '**/{browser,electron-browser,electron-main,electron-utility}/**/*.test.js', '**/vs/platform/environment/test/node/nativeModules.test.js', // native modules are compiled against Electron and this test would fail with node.js '**/vs/base/parts/storage/test/node/storage.test.js', // same as above, due to direct dependency to sqlite native module '**/vs/workbench/contrib/testing/test/**' // flaky (https://github.com/microsoft/vscode/issues/137853)