diff --git a/.vscode/settings.json b/.vscode/settings.json index d9aeed8443206..059bcb77f5f2d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "scripts/test-integration.bat": true, "scripts/test-integration.sh": true, }, + "chat.viewSessions.enabled": true, // --- Editor --- "editor.insertSpaces": false, diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index 187100b563fec..5c73304b4d891 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -33,7 +33,7 @@ const dataUrlsNotValid = l10n.t("Data URLs are not a valid image source."); const relativeUrlRequiresHttpsRepository = l10n.t("Relative image URLs require a repository with HTTPS protocol to be specified in the package.json."); const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); -const bumpEngineForImplicitActivationEvents = l10n.t("This activation event can be removed for extensions targeting engine version ^1.75.0 as VS Code will generate these automatically from your package.json contribution declarations."); + const starActivation = l10n.t("Using '*' activation is usually a bad idea as it impacts performance."); const parsingErrorHeader = l10n.t("Error parsing the when-clause:"); @@ -162,13 +162,12 @@ export class ExtensionLinter { if (activationEventsNode?.type === 'array' && activationEventsNode.children) { for (const activationEventNode of activationEventsNode.children) { const activationEvent = getNodeValue(activationEventNode); - const isImplicitActivationSupported = info.engineVersion && info.engineVersion?.majorBase >= 1 && info.engineVersion?.minorBase >= 75; + const isImplicitActivationSupported = info.engineVersion && (info.engineVersion.majorBase > 1 || (info.engineVersion.majorBase === 1 && info.engineVersion.minorBase >= 75)); // Redundant Implicit Activation - if (info.implicitActivationEvents?.has(activationEvent) && redundantImplicitActivationEventPrefixes.some((prefix) => activationEvent.startsWith(prefix))) { + if (isImplicitActivationSupported && info.implicitActivationEvents?.has(activationEvent) && redundantImplicitActivationEventPrefixes.some((prefix) => activationEvent.startsWith(prefix))) { const start = document.positionAt(activationEventNode.offset); const end = document.positionAt(activationEventNode.offset + activationEventNode.length); - const message = isImplicitActivationSupported ? redundantImplicitActivationEvent : bumpEngineForImplicitActivationEvents; - diagnostics.push(new Diagnostic(new Range(start, end), message, isImplicitActivationSupported ? DiagnosticSeverity.Warning : DiagnosticSeverity.Information)); + diagnostics.push(new Diagnostic(new Range(start, end), redundantImplicitActivationEvent, DiagnosticSeverity.Warning)); } // Reserved Implicit Activation diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index b528a89cff06d..24d2dae8040b5 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1928,36 +1928,26 @@ export class Repository implements Disposable { try { // Copy files - let copiedFiles = 0; - const results = await window.withProgress({ - location: ProgressLocation.Notification, - title: l10n.t('Copying additional files to the worktree'), - cancellable: false - }, async (progress) => { - const limiter = new Limiter(10); - const files = Array.from(ignoredFiles); - - return Promise.allSettled(files.map(sourceFile => - limiter.queue(async () => { - const targetFile = path.join(worktreePath, relativePath(this.root, sourceFile)); - await fsPromises.mkdir(path.dirname(targetFile), { recursive: true }); - await fsPromises.cp(sourceFile, targetFile, { - force: true, - recursive: false, - verbatimSymlinks: true - }); - - copiedFiles++; - progress.report({ - increment: 100 / ignoredFiles.size, - message: l10n.t('({0}/{1})', copiedFiles, ignoredFiles.size) - }); - }) - )); - }); + const startTime = Date.now(); + const limiter = new Limiter(15); + const files = Array.from(ignoredFiles); + + const results = await Promise.allSettled(files.map(sourceFile => + limiter.queue(async () => { + const targetFile = path.join(worktreePath, relativePath(this.root, sourceFile)); + await fsPromises.mkdir(path.dirname(targetFile), { recursive: true }); + await fsPromises.cp(sourceFile, targetFile, { + force: true, + mode: fs.constants.COPYFILE_FICLONE, + recursive: false, + verbatimSymlinks: true + }); + }) + )); // Log any failed operations const failedOperations = results.filter(r => r.status === 'rejected'); + this.logger.info(`[Repository][_copyWorktreeIncludeFiles] Copied ${files.length - failedOperations.length} files to worktree. Failed to copy ${failedOperations.length} files. [${Date.now() - startTime}ms]`); if (failedOperations.length > 0) { window.showWarningMessage(l10n.t('Failed to copy {0} files to the worktree.', failedOperations.length)); diff --git a/package-lock.json b/package-lock.json index 74e77c2e608de..b3bc0bec88cfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,16 +28,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.101", - "@xterm/addon-image": "^0.10.0-beta.101", - "@xterm/addon-ligatures": "^0.11.0-beta.101", - "@xterm/addon-progress": "^0.3.0-beta.101", - "@xterm/addon-search": "^0.17.0-beta.101", - "@xterm/addon-serialize": "^0.15.0-beta.101", - "@xterm/addon-unicode11": "^0.10.0-beta.101", - "@xterm/addon-webgl": "^0.20.0-beta.100", - "@xterm/headless": "^6.1.0-beta.101", - "@xterm/xterm": "^6.1.0-beta.101", + "@xterm/addon-clipboard": "^0.3.0-beta.102", + "@xterm/addon-image": "^0.10.0-beta.102", + "@xterm/addon-ligatures": "^0.11.0-beta.102", + "@xterm/addon-progress": "^0.3.0-beta.102", + "@xterm/addon-search": "^0.17.0-beta.102", + "@xterm/addon-serialize": "^0.15.0-beta.102", + "@xterm/addon-unicode11": "^0.10.0-beta.102", + "@xterm/addon-webgl": "^0.20.0-beta.101", + "@xterm/headless": "^6.1.0-beta.102", + "@xterm/xterm": "^6.1.0-beta.102", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -3897,30 +3897,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.101.tgz", - "integrity": "sha512-xuEqMUlvC6UR4HEa1OHSgF0LUEH7K5rS0fYjMJ9Tj/9Fsb84Z9LWwk5O5kYB4njEToX+mbm78Dhy7huXUcs8Ug==", + "version": "0.3.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.102.tgz", + "integrity": "sha512-VIqI/GP/OF4XX2nZaub3HJ59ysfR/t1BTy9695zgTecmX+RXPqp4s4rPmy3yv1k1MK/fqPu/HITGHjGvDZgKdg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.101.tgz", - "integrity": "sha512-Gd8ZpfyzvisG+08+mXynufQHfaWWxGhhtRMSQXV2FyPNa3MNXNrowgjeXhpaRObOOsxSZnAlB8qDW8OHTjzG6A==", + "version": "0.10.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.102.tgz", + "integrity": "sha512-1zoOF08yrtXyK8YDd9h0PA7IFoaTyygV0Cip2/6fQb7j9QD6TMrqWsNC+g8T8yfLB31q6NS5CnG+TELVoNlfnw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.101.tgz", - "integrity": "sha512-FIO3S/f3K1nnmQs/oJ3ILI9p1Vb4sSK7J4UhROBj5JOyZtmRwhdUFr9MozfPVdc2VZPRJJJJq6vaPDdLioeJyQ==", + "version": "0.11.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.102.tgz", + "integrity": "sha512-P6o8BBv4+gaAp7JNsiHyoMb1NKkW/KSMJyY6H7nhgUMtY6dT+skp3fbIkzP2M57XOKziLK9K3oRB0OcHjivrHQ==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -3930,7 +3930,7 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-ligatures/node_modules/lru-cache": { @@ -3952,63 +3952,63 @@ "license": "ISC" }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.101.tgz", - "integrity": "sha512-QsJLvH3tc9KywOXb9O/mxkPoYmL2cCvKUa/YckriuZmEh5WMw+3cgNa+BC0aA73htdWE8UMUBvigE786KErajA==", + "version": "0.3.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.102.tgz", + "integrity": "sha512-Mcsse/9drif/4OuzB10SpkiWmXJObWs+wDPRYtgH9uX4cK31Z2Y+Fm6UMnsDw9taIRMmizhhKYbgR+CBJGSoZA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.101.tgz", - "integrity": "sha512-Y1yWv2baZdqP6AH/aKmMXSs6VNr7FGrpOe+DKNKq77H+QZbUoUXfgI/qKZpqlByl7FGuo8OT9g0AqH3zykkkUw==", + "version": "0.17.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.102.tgz", + "integrity": "sha512-61XYIk/Mbxc1gEL4pBVSTbxu1IV0kLtNrc8JHqp7rgVWKyNQMVKwovMUuzx2yR4y1h/Obu+P+OS06zsbrExg8A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.101.tgz", - "integrity": "sha512-NJXY4WBV9tG2IG85xMlmUX6YFIa63NO0qF+JzDM9+3AaNDEmTXd+Lg5ucWtJjZRDW3AHUOLAQos4osLvBfyhYQ==", + "version": "0.15.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.102.tgz", + "integrity": "sha512-fm5UA65Lk+ScucYtuZRsuFWI2MAUNYUh3w+9qOlfJRnoxP0PgsiCxXgXO6T6zB3/K9tO3De/X7PcfKvgP6zPZg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.101.tgz", - "integrity": "sha512-WM9Rs8ZBQ1nY5nb4OxXsOLVIZ2DQvGmtSIKkUueDB9mSQOg05mz2dHbEdW63MrX8sMQAbEx+o/kyxvl7oFDS/A==", + "version": "0.10.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.102.tgz", + "integrity": "sha512-+h/tIFEkGbTSlKCM9u3+PWs2P3bQWDYshy3JeJ+kYzgKdsCqfu8PNdQDekVVT/SeqW17hpQKl2OWHfIsNix9Xw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.100", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.100.tgz", - "integrity": "sha512-h17XiyERE+LpuYEPUAm2ux6g2Iy34BT/tfwxOckZ+RrhjM8bZMeN3u6/q28viBqAKWhhD3JbxlcDfKMN8sE3sg==", + "version": "0.20.0-beta.101", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.101.tgz", + "integrity": "sha512-X6u76IQ/yFkWgX0Qh7OjdHJzDB/Wu03ZfMTRb/ipUCx7J0w/6Xs8TQ/wqSCLyE8JeZC/Pshhrg+acUcJLwC8Mw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/headless": { - "version": "6.1.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.101.tgz", - "integrity": "sha512-m+5Gyiy72wry8wJQPueUojcF8bMzK983owwOCyFp0I6qrHk+VuKh84FQXAvq5Gu9C0irL99iP5K54xGjDZ7Zkg==", + "version": "6.1.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.102.tgz", + "integrity": "sha512-aiuSRacrnCHf1a/eMlB+3wRhLJ4Iy4NMPVCnHbB0KV+eIoMIGmdUAXJvrFkm2Qd4mF4wXlb6okdtFievi+g5Fw==", "license": "MIT", "workspaces": [ "addons/*" ] }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.101.tgz", - "integrity": "sha512-fINmTdz6WkLkMkwwpwuWu4h1gn4uQVnnJJsdKYy+Wwr7mzazx2uK22Lrgc6B/2AZZjw+CfK2mkK5+AuR5J2YRw==", + "version": "6.1.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.102.tgz", + "integrity": "sha512-53vBNI1onToMiVCxh+pq1QkS8w3fEdeGfN/wp76GitHNgkLSDeTghGITsHeXGoEEgbnhR7+HhX4b5TGdN6u5vw==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/package.json b/package.json index b76374ed584bf..a6112b973122c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.109.0", - "distro": "f44449f84806363760ce8bb8dbe85cd8207498ff", + "distro": "b90415e4e25274537a83463c7af88fca7e9528a7", "author": { "name": "Microsoft Corporation" }, @@ -91,16 +91,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.101", - "@xterm/addon-image": "^0.10.0-beta.101", - "@xterm/addon-ligatures": "^0.11.0-beta.101", - "@xterm/addon-progress": "^0.3.0-beta.101", - "@xterm/addon-search": "^0.17.0-beta.101", - "@xterm/addon-serialize": "^0.15.0-beta.101", - "@xterm/addon-unicode11": "^0.10.0-beta.101", - "@xterm/addon-webgl": "^0.20.0-beta.100", - "@xterm/headless": "^6.1.0-beta.101", - "@xterm/xterm": "^6.1.0-beta.101", + "@xterm/addon-clipboard": "^0.3.0-beta.102", + "@xterm/addon-image": "^0.10.0-beta.102", + "@xterm/addon-ligatures": "^0.11.0-beta.102", + "@xterm/addon-progress": "^0.3.0-beta.102", + "@xterm/addon-search": "^0.17.0-beta.102", + "@xterm/addon-serialize": "^0.15.0-beta.102", + "@xterm/addon-unicode11": "^0.10.0-beta.102", + "@xterm/addon-webgl": "^0.20.0-beta.101", + "@xterm/headless": "^6.1.0-beta.102", + "@xterm/xterm": "^6.1.0-beta.102", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", diff --git a/remote/package-lock.json b/remote/package-lock.json index b651afca5e41f..cbaead12270a8 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -21,16 +21,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.101", - "@xterm/addon-image": "^0.10.0-beta.101", - "@xterm/addon-ligatures": "^0.11.0-beta.101", - "@xterm/addon-progress": "^0.3.0-beta.101", - "@xterm/addon-search": "^0.17.0-beta.101", - "@xterm/addon-serialize": "^0.15.0-beta.101", - "@xterm/addon-unicode11": "^0.10.0-beta.101", - "@xterm/addon-webgl": "^0.20.0-beta.100", - "@xterm/headless": "^6.1.0-beta.101", - "@xterm/xterm": "^6.1.0-beta.101", + "@xterm/addon-clipboard": "^0.3.0-beta.102", + "@xterm/addon-image": "^0.10.0-beta.102", + "@xterm/addon-ligatures": "^0.11.0-beta.102", + "@xterm/addon-progress": "^0.3.0-beta.102", + "@xterm/addon-search": "^0.17.0-beta.102", + "@xterm/addon-serialize": "^0.15.0-beta.102", + "@xterm/addon-unicode11": "^0.10.0-beta.102", + "@xterm/addon-webgl": "^0.20.0-beta.101", + "@xterm/headless": "^6.1.0-beta.102", + "@xterm/xterm": "^6.1.0-beta.102", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -526,30 +526,30 @@ "license": "MIT" }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.101.tgz", - "integrity": "sha512-xuEqMUlvC6UR4HEa1OHSgF0LUEH7K5rS0fYjMJ9Tj/9Fsb84Z9LWwk5O5kYB4njEToX+mbm78Dhy7huXUcs8Ug==", + "version": "0.3.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.102.tgz", + "integrity": "sha512-VIqI/GP/OF4XX2nZaub3HJ59ysfR/t1BTy9695zgTecmX+RXPqp4s4rPmy3yv1k1MK/fqPu/HITGHjGvDZgKdg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.101.tgz", - "integrity": "sha512-Gd8ZpfyzvisG+08+mXynufQHfaWWxGhhtRMSQXV2FyPNa3MNXNrowgjeXhpaRObOOsxSZnAlB8qDW8OHTjzG6A==", + "version": "0.10.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.102.tgz", + "integrity": "sha512-1zoOF08yrtXyK8YDd9h0PA7IFoaTyygV0Cip2/6fQb7j9QD6TMrqWsNC+g8T8yfLB31q6NS5CnG+TELVoNlfnw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.101.tgz", - "integrity": "sha512-FIO3S/f3K1nnmQs/oJ3ILI9p1Vb4sSK7J4UhROBj5JOyZtmRwhdUFr9MozfPVdc2VZPRJJJJq6vaPDdLioeJyQ==", + "version": "0.11.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.102.tgz", + "integrity": "sha512-P6o8BBv4+gaAp7JNsiHyoMb1NKkW/KSMJyY6H7nhgUMtY6dT+skp3fbIkzP2M57XOKziLK9K3oRB0OcHjivrHQ==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -559,67 +559,67 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.101.tgz", - "integrity": "sha512-QsJLvH3tc9KywOXb9O/mxkPoYmL2cCvKUa/YckriuZmEh5WMw+3cgNa+BC0aA73htdWE8UMUBvigE786KErajA==", + "version": "0.3.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.102.tgz", + "integrity": "sha512-Mcsse/9drif/4OuzB10SpkiWmXJObWs+wDPRYtgH9uX4cK31Z2Y+Fm6UMnsDw9taIRMmizhhKYbgR+CBJGSoZA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.101.tgz", - "integrity": "sha512-Y1yWv2baZdqP6AH/aKmMXSs6VNr7FGrpOe+DKNKq77H+QZbUoUXfgI/qKZpqlByl7FGuo8OT9g0AqH3zykkkUw==", + "version": "0.17.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.102.tgz", + "integrity": "sha512-61XYIk/Mbxc1gEL4pBVSTbxu1IV0kLtNrc8JHqp7rgVWKyNQMVKwovMUuzx2yR4y1h/Obu+P+OS06zsbrExg8A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.101.tgz", - "integrity": "sha512-NJXY4WBV9tG2IG85xMlmUX6YFIa63NO0qF+JzDM9+3AaNDEmTXd+Lg5ucWtJjZRDW3AHUOLAQos4osLvBfyhYQ==", + "version": "0.15.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.102.tgz", + "integrity": "sha512-fm5UA65Lk+ScucYtuZRsuFWI2MAUNYUh3w+9qOlfJRnoxP0PgsiCxXgXO6T6zB3/K9tO3De/X7PcfKvgP6zPZg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.101.tgz", - "integrity": "sha512-WM9Rs8ZBQ1nY5nb4OxXsOLVIZ2DQvGmtSIKkUueDB9mSQOg05mz2dHbEdW63MrX8sMQAbEx+o/kyxvl7oFDS/A==", + "version": "0.10.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.102.tgz", + "integrity": "sha512-+h/tIFEkGbTSlKCM9u3+PWs2P3bQWDYshy3JeJ+kYzgKdsCqfu8PNdQDekVVT/SeqW17hpQKl2OWHfIsNix9Xw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.100", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.100.tgz", - "integrity": "sha512-h17XiyERE+LpuYEPUAm2ux6g2Iy34BT/tfwxOckZ+RrhjM8bZMeN3u6/q28viBqAKWhhD3JbxlcDfKMN8sE3sg==", + "version": "0.20.0-beta.101", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.101.tgz", + "integrity": "sha512-X6u76IQ/yFkWgX0Qh7OjdHJzDB/Wu03ZfMTRb/ipUCx7J0w/6Xs8TQ/wqSCLyE8JeZC/Pshhrg+acUcJLwC8Mw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/headless": { - "version": "6.1.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.101.tgz", - "integrity": "sha512-m+5Gyiy72wry8wJQPueUojcF8bMzK983owwOCyFp0I6qrHk+VuKh84FQXAvq5Gu9C0irL99iP5K54xGjDZ7Zkg==", + "version": "6.1.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.102.tgz", + "integrity": "sha512-aiuSRacrnCHf1a/eMlB+3wRhLJ4Iy4NMPVCnHbB0KV+eIoMIGmdUAXJvrFkm2Qd4mF4wXlb6okdtFievi+g5Fw==", "license": "MIT", "workspaces": [ "addons/*" ] }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.101.tgz", - "integrity": "sha512-fINmTdz6WkLkMkwwpwuWu4h1gn4uQVnnJJsdKYy+Wwr7mzazx2uK22Lrgc6B/2AZZjw+CfK2mkK5+AuR5J2YRw==", + "version": "6.1.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.102.tgz", + "integrity": "sha512-53vBNI1onToMiVCxh+pq1QkS8w3fEdeGfN/wp76GitHNgkLSDeTghGITsHeXGoEEgbnhR7+HhX4b5TGdN6u5vw==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/remote/package.json b/remote/package.json index 180f3b668f915..059c59c724117 100644 --- a/remote/package.json +++ b/remote/package.json @@ -16,16 +16,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.101", - "@xterm/addon-image": "^0.10.0-beta.101", - "@xterm/addon-ligatures": "^0.11.0-beta.101", - "@xterm/addon-progress": "^0.3.0-beta.101", - "@xterm/addon-search": "^0.17.0-beta.101", - "@xterm/addon-serialize": "^0.15.0-beta.101", - "@xterm/addon-unicode11": "^0.10.0-beta.101", - "@xterm/addon-webgl": "^0.20.0-beta.100", - "@xterm/headless": "^6.1.0-beta.101", - "@xterm/xterm": "^6.1.0-beta.101", + "@xterm/addon-clipboard": "^0.3.0-beta.102", + "@xterm/addon-image": "^0.10.0-beta.102", + "@xterm/addon-ligatures": "^0.11.0-beta.102", + "@xterm/addon-progress": "^0.3.0-beta.102", + "@xterm/addon-search": "^0.17.0-beta.102", + "@xterm/addon-serialize": "^0.15.0-beta.102", + "@xterm/addon-unicode11": "^0.10.0-beta.102", + "@xterm/addon-webgl": "^0.20.0-beta.101", + "@xterm/headless": "^6.1.0-beta.102", + "@xterm/xterm": "^6.1.0-beta.102", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index fcdd633aa25d9..78bef5ccc5d5f 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -13,15 +13,15 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.3.0-beta.101", - "@xterm/addon-image": "^0.10.0-beta.101", - "@xterm/addon-ligatures": "^0.11.0-beta.101", - "@xterm/addon-progress": "^0.3.0-beta.101", - "@xterm/addon-search": "^0.17.0-beta.101", - "@xterm/addon-serialize": "^0.15.0-beta.101", - "@xterm/addon-unicode11": "^0.10.0-beta.101", - "@xterm/addon-webgl": "^0.20.0-beta.100", - "@xterm/xterm": "^6.1.0-beta.101", + "@xterm/addon-clipboard": "^0.3.0-beta.102", + "@xterm/addon-image": "^0.10.0-beta.102", + "@xterm/addon-ligatures": "^0.11.0-beta.102", + "@xterm/addon-progress": "^0.3.0-beta.102", + "@xterm/addon-search": "^0.17.0-beta.102", + "@xterm/addon-serialize": "^0.15.0-beta.102", + "@xterm/addon-unicode11": "^0.10.0-beta.102", + "@xterm/addon-webgl": "^0.20.0-beta.101", + "@xterm/xterm": "^6.1.0-beta.102", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client": "0.3.1", @@ -92,30 +92,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.101.tgz", - "integrity": "sha512-xuEqMUlvC6UR4HEa1OHSgF0LUEH7K5rS0fYjMJ9Tj/9Fsb84Z9LWwk5O5kYB4njEToX+mbm78Dhy7huXUcs8Ug==", + "version": "0.3.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.102.tgz", + "integrity": "sha512-VIqI/GP/OF4XX2nZaub3HJ59ysfR/t1BTy9695zgTecmX+RXPqp4s4rPmy3yv1k1MK/fqPu/HITGHjGvDZgKdg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.101.tgz", - "integrity": "sha512-Gd8ZpfyzvisG+08+mXynufQHfaWWxGhhtRMSQXV2FyPNa3MNXNrowgjeXhpaRObOOsxSZnAlB8qDW8OHTjzG6A==", + "version": "0.10.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.102.tgz", + "integrity": "sha512-1zoOF08yrtXyK8YDd9h0PA7IFoaTyygV0Cip2/6fQb7j9QD6TMrqWsNC+g8T8yfLB31q6NS5CnG+TELVoNlfnw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.101.tgz", - "integrity": "sha512-FIO3S/f3K1nnmQs/oJ3ILI9p1Vb4sSK7J4UhROBj5JOyZtmRwhdUFr9MozfPVdc2VZPRJJJJq6vaPDdLioeJyQ==", + "version": "0.11.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.102.tgz", + "integrity": "sha512-P6o8BBv4+gaAp7JNsiHyoMb1NKkW/KSMJyY6H7nhgUMtY6dT+skp3fbIkzP2M57XOKziLK9K3oRB0OcHjivrHQ==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -125,58 +125,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.101.tgz", - "integrity": "sha512-QsJLvH3tc9KywOXb9O/mxkPoYmL2cCvKUa/YckriuZmEh5WMw+3cgNa+BC0aA73htdWE8UMUBvigE786KErajA==", + "version": "0.3.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.102.tgz", + "integrity": "sha512-Mcsse/9drif/4OuzB10SpkiWmXJObWs+wDPRYtgH9uX4cK31Z2Y+Fm6UMnsDw9taIRMmizhhKYbgR+CBJGSoZA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.101.tgz", - "integrity": "sha512-Y1yWv2baZdqP6AH/aKmMXSs6VNr7FGrpOe+DKNKq77H+QZbUoUXfgI/qKZpqlByl7FGuo8OT9g0AqH3zykkkUw==", + "version": "0.17.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.102.tgz", + "integrity": "sha512-61XYIk/Mbxc1gEL4pBVSTbxu1IV0kLtNrc8JHqp7rgVWKyNQMVKwovMUuzx2yR4y1h/Obu+P+OS06zsbrExg8A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.101.tgz", - "integrity": "sha512-NJXY4WBV9tG2IG85xMlmUX6YFIa63NO0qF+JzDM9+3AaNDEmTXd+Lg5ucWtJjZRDW3AHUOLAQos4osLvBfyhYQ==", + "version": "0.15.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.102.tgz", + "integrity": "sha512-fm5UA65Lk+ScucYtuZRsuFWI2MAUNYUh3w+9qOlfJRnoxP0PgsiCxXgXO6T6zB3/K9tO3De/X7PcfKvgP6zPZg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.101.tgz", - "integrity": "sha512-WM9Rs8ZBQ1nY5nb4OxXsOLVIZ2DQvGmtSIKkUueDB9mSQOg05mz2dHbEdW63MrX8sMQAbEx+o/kyxvl7oFDS/A==", + "version": "0.10.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.102.tgz", + "integrity": "sha512-+h/tIFEkGbTSlKCM9u3+PWs2P3bQWDYshy3JeJ+kYzgKdsCqfu8PNdQDekVVT/SeqW17hpQKl2OWHfIsNix9Xw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.100", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.100.tgz", - "integrity": "sha512-h17XiyERE+LpuYEPUAm2ux6g2Iy34BT/tfwxOckZ+RrhjM8bZMeN3u6/q28viBqAKWhhD3JbxlcDfKMN8sE3sg==", + "version": "0.20.0-beta.101", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.101.tgz", + "integrity": "sha512-X6u76IQ/yFkWgX0Qh7OjdHJzDB/Wu03ZfMTRb/ipUCx7J0w/6Xs8TQ/wqSCLyE8JeZC/Pshhrg+acUcJLwC8Mw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.101" + "@xterm/xterm": "^6.1.0-beta.102" } }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.101.tgz", - "integrity": "sha512-fINmTdz6WkLkMkwwpwuWu4h1gn4uQVnnJJsdKYy+Wwr7mzazx2uK22Lrgc6B/2AZZjw+CfK2mkK5+AuR5J2YRw==", + "version": "6.1.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.102.tgz", + "integrity": "sha512-53vBNI1onToMiVCxh+pq1QkS8w3fEdeGfN/wp76GitHNgkLSDeTghGITsHeXGoEEgbnhR7+HhX4b5TGdN6u5vw==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/remote/web/package.json b/remote/web/package.json index 20b48882695a6..e91442d01d77f 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -8,15 +8,15 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.3.0-beta.101", - "@xterm/addon-image": "^0.10.0-beta.101", - "@xterm/addon-ligatures": "^0.11.0-beta.101", - "@xterm/addon-progress": "^0.3.0-beta.101", - "@xterm/addon-search": "^0.17.0-beta.101", - "@xterm/addon-serialize": "^0.15.0-beta.101", - "@xterm/addon-unicode11": "^0.10.0-beta.101", - "@xterm/addon-webgl": "^0.20.0-beta.100", - "@xterm/xterm": "^6.1.0-beta.101", + "@xterm/addon-clipboard": "^0.3.0-beta.102", + "@xterm/addon-image": "^0.10.0-beta.102", + "@xterm/addon-ligatures": "^0.11.0-beta.102", + "@xterm/addon-progress": "^0.3.0-beta.102", + "@xterm/addon-search": "^0.17.0-beta.102", + "@xterm/addon-serialize": "^0.15.0-beta.102", + "@xterm/addon-unicode11": "^0.10.0-beta.102", + "@xterm/addon-webgl": "^0.20.0-beta.101", + "@xterm/xterm": "^6.1.0-beta.102", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client": "0.3.1", diff --git a/src/vs/editor/common/model/tokens/annotations.ts b/src/vs/editor/common/model/tokens/annotations.ts index cd76386880160..cf3943442dc27 100644 --- a/src/vs/editor/common/model/tokens/annotations.ts +++ b/src/vs/editor/common/model/tokens/annotations.ts @@ -84,7 +84,7 @@ export class AnnotatedString implements IAnnotatedString { startIndex = startIndexWhereToReplace; } else { const candidate = this._annotations[- (startIndexWhereToReplace + 2)]?.range; - if (candidate && offset >= candidate.start && offset <= candidate.endExclusive) { + if (candidate && offset >= candidate.start && offset < candidate.endExclusive) { startIndex = - (startIndexWhereToReplace + 2); } else { startIndex = - (startIndexWhereToReplace + 1); @@ -103,7 +103,7 @@ export class AnnotatedString implements IAnnotatedString { endIndexExclusive = endIndexWhereToReplace + 1; } else { const candidate = this._annotations[-(endIndexWhereToReplace + 1)]?.range; - if (candidate && offset >= candidate.start && offset <= candidate.endExclusive) { + if (candidate && offset > candidate.start && offset <= candidate.endExclusive) { endIndexExclusive = - endIndexWhereToReplace; } else { endIndexExclusive = - (endIndexWhereToReplace + 1); diff --git a/src/vs/editor/common/model/tokens/tokenizationFontDecorationsProvider.ts b/src/vs/editor/common/model/tokens/tokenizationFontDecorationsProvider.ts index 0ab0c461ed0ae..9b4f9996262e1 100644 --- a/src/vs/editor/common/model/tokens/tokenizationFontDecorationsProvider.ts +++ b/src/vs/editor/common/model/tokens/tokenizationFontDecorationsProvider.ts @@ -49,12 +49,6 @@ export class TokenizationFontDecorationProvider extends Disposable implements De for (const annotation of fontChanges.changes.annotations) { const startPosition = this.textModel.getPositionAt(annotation.range.start); - const endPosition = this.textModel.getPositionAt(annotation.range.endExclusive); - - if (startPosition.lineNumber !== endPosition.lineNumber) { - // The token should be always on a single line - continue; - } const lineNumber = startPosition.lineNumber; let fontTokenAnnotation: IAnnotationUpdate; diff --git a/src/vs/editor/contrib/gotoError/browser/gotoError.ts b/src/vs/editor/contrib/gotoError/browser/gotoError.ts index eea7aa4931a6b..6c71c87435aad 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoError.ts @@ -190,7 +190,7 @@ class MarkerNavigationAction extends EditorAction { } export class NextMarkerAction extends MarkerNavigationAction { - static ID: string = 'editor.action.marker.next'; + static readonly ID = 'editor.action.marker.next'; static LABEL = nls.localize2('markerAction.next.label', "Go to Next Problem (Error, Warning, Info)"); constructor() { super(true, false, { @@ -213,8 +213,8 @@ export class NextMarkerAction extends MarkerNavigationAction { } } -class PrevMarkerAction extends MarkerNavigationAction { - static ID: string = 'editor.action.marker.prev'; +export class PrevMarkerAction extends MarkerNavigationAction { + static readonly ID = 'editor.action.marker.prev'; static LABEL = nls.localize2('markerAction.previous.label', "Go to Previous Problem (Error, Warning, Info)"); constructor() { super(false, false, { @@ -237,10 +237,11 @@ class PrevMarkerAction extends MarkerNavigationAction { } } -class NextMarkerInFilesAction extends MarkerNavigationAction { +export class NextMarkerInFilesAction extends MarkerNavigationAction { + static readonly ID = 'editor.action.marker.nextInFiles'; constructor() { super(true, true, { - id: 'editor.action.marker.nextInFiles', + id: NextMarkerInFilesAction.ID, label: nls.localize2('markerAction.nextInFiles.label', "Go to Next Problem in Files (Error, Warning, Info)"), precondition: undefined, kbOpts: { @@ -258,10 +259,11 @@ class NextMarkerInFilesAction extends MarkerNavigationAction { } } -class PrevMarkerInFilesAction extends MarkerNavigationAction { +export class PrevMarkerInFilesAction extends MarkerNavigationAction { + static readonly ID = 'editor.action.marker.prevInFiles'; constructor() { super(false, true, { - id: 'editor.action.marker.prevInFiles', + id: PrevMarkerInFilesAction.ID, label: nls.localize2('markerAction.previousInFiles.label', "Go to Previous Problem in Files (Error, Warning, Info)"), precondition: undefined, kbOpts: { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 4c681c9719de1..7a0c70e0b0d4c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -32,6 +32,7 @@ import { CursorChangeReason } from '../../../../common/cursorEvents.js'; import { ILanguageFeatureDebounceService } from '../../../../common/services/languageFeatureDebounce.js'; import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; import { FIND_IDS } from '../../../find/browser/findModel.js'; +import { NextMarkerAction, NextMarkerInFilesAction, PrevMarkerAction, PrevMarkerInFilesAction } from '../../../gotoError/browser/gotoError.js'; import { InsertLineAfterAction, InsertLineBeforeAction } from '../../../linesOperations/browser/linesOperations.js'; import { InlineSuggestionHintsContentWidget } from '../hintsWidget/inlineCompletionsHintsWidget.js'; import { TextModelChangeRecorder } from '../model/changeRecorder.js'; @@ -224,6 +225,10 @@ export class InlineCompletionsController extends Disposable { InsertLineAfterAction.ID, InsertLineBeforeAction.ID, FIND_IDS.NextMatchFindAction, + NextMarkerAction.ID, + PrevMarkerAction.ID, + NextMarkerInFilesAction.ID, + PrevMarkerInFilesAction.ID, ...TriggerInlineEditCommandsRegistry.getRegisteredCommands(), ]); this._register(this._commandService.onDidExecuteCommand((e) => { diff --git a/src/vs/editor/test/common/model/annotations.test.ts b/src/vs/editor/test/common/model/annotations.test.ts index 6f5bc3b12dd36..133cb256e1a96 100644 --- a/src/vs/editor/test/common/model/annotations.test.ts +++ b/src/vs/editor/test/common/model/annotations.test.ts @@ -332,19 +332,19 @@ suite('Annotations Suite', () => { assert.strictEqual(result1.length, 2); assert.deepStrictEqual(result1.map(a => a.annotation), ['1', '2']); const result2 = vas.getAnnotationsIntersecting(new OffsetRange(0, 22)); - assert.strictEqual(result2.length, 3); - assert.deepStrictEqual(result2.map(a => a.annotation), ['1', '2', '3']); + assert.strictEqual(result2.length, 2); + assert.deepStrictEqual(result2.map(a => a.annotation), ['1', '2']); }); test('getAnnotationsIntersecting 2', () => { const vas = fromVisual('[1:Lorem] [2:i]p[3:s]'); const result1 = vas.getAnnotationsIntersecting(new OffsetRange(5, 7)); - assert.strictEqual(result1.length, 2); - assert.deepStrictEqual(result1.map(a => a.annotation), ['1', '2']); + assert.strictEqual(result1.length, 1); + assert.deepStrictEqual(result1.map(a => a.annotation), ['2']); const result2 = vas.getAnnotationsIntersecting(new OffsetRange(5, 9)); - assert.strictEqual(result2.length, 3); - assert.deepStrictEqual(result2.map(a => a.annotation), ['1', '2', '3']); + assert.strictEqual(result2.length, 2); + assert.deepStrictEqual(result2.map(a => a.annotation), ['2', '3']); }); test('getAnnotationsIntersecting 3', () => { @@ -370,8 +370,8 @@ suite('Annotations Suite', () => { test('getAnnotationsIntersecting 5', () => { const vas = fromVisual('[1:Lorem ipsum] [2:dol] [3:or]'); const result = vas.getAnnotationsIntersecting(new OffsetRange(1, 16)); - assert.strictEqual(result.length, 3); - assert.deepStrictEqual(result.map(a => a.annotation), ['1', '2', '3']); + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result.map(a => a.annotation), ['1', '2']); }); test('applyEdit 1 - deletion within annotation', () => { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 7bcbfe658455f..88bc828b318b2 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1198,6 +1198,7 @@ export class ChangeLanguageAction extends Action2 { } return { + id: languageId, label: languageName, meta: extensions, iconClasses: getIconClassesForLanguageId(languageId), @@ -1280,8 +1281,7 @@ export class ChangeLanguageAction extends Action2 { } } } else { - const languageId = languageService.getLanguageIdByLanguageName(pick.label); - languageSelection = languageService.createById(languageId); + languageSelection = languageService.createById(pick.id); if (resource) { // fire and forget to not slow things down diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts index 7c67a0de0d519..1c525a8eaa016 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts @@ -149,10 +149,10 @@ export function registerNewChatActions() { await editingSession?.stop(); // Create a new session with the same type as the current session - if (isIChatViewViewContext(widget.viewContext)) { + const currentResource = widget.viewModel?.model.sessionResource; + const sessionType = currentResource ? getChatSessionType(currentResource) : localChatSessionType; + if (isIChatViewViewContext(widget.viewContext) && sessionType !== localChatSessionType) { // For the sidebar, we need to explicitly load a session with the same type - const currentResource = widget.viewModel?.model.sessionResource; - const sessionType = currentResource ? getChatSessionType(currentResource) : localChatSessionType; const newResource = getResourceForNewChatSession(sessionType); const view = await viewsService.openView(ChatViewId) as ChatViewPane; await view.loadSession(newResource); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts index adb1ea5638cf7..e9a78c12610ab 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts @@ -17,10 +17,10 @@ import { IAgentSessionsService, AgentSessionsService } from './agentSessionsServ import { LocalAgentsSessionsProvider } from './localAgentSessionsProvider.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../common/contributions.js'; import { ISubmenuItem, MenuId, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js'; -import { ArchiveAgentSessionAction, ArchiveAgentSessionSectionAction, UnarchiveAgentSessionSectionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, ToggleAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, MarkAgentSessionSectionReadAction, FocusAgentSessionsAction, SetAgentSessionsOrientationStackedAction, SetAgentSessionsOrientationSideBySideAction, ToggleChatViewSessionsAction, PickAgentSessionAction, ArchiveAllAgentSessionsAction, RenameAgentSessionAction, DeleteAgentSessionAction, DeleteAllLocalSessionsAction } from './agentSessionsActions.js'; +import { ArchiveAgentSessionAction, ArchiveAgentSessionSectionAction, UnarchiveAgentSessionSectionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, ToggleAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, MarkAgentSessionSectionReadAction, FocusAgentSessionsAction, SetAgentSessionsOrientationStackedAction, SetAgentSessionsOrientationSideBySideAction, ShowAllAgentSessionsAction, ShowRecentAgentSessionsAction, HideAgentSessionsAction, PickAgentSessionAction, ArchiveAllAgentSessionsAction, RenameAgentSessionAction, DeleteAgentSessionAction, DeleteAllLocalSessionsAction } from './agentSessionsActions.js'; import { AgentSessionsQuickAccessProvider, AGENT_SESSIONS_QUICK_ACCESS_PREFIX } from './agentSessionsQuickAccess.js'; import { IAgentSessionProjectionService, AgentSessionProjectionService } from './agentSessionProjectionService.js'; -import { EnterAgentSessionProjectionAction, ExitAgentSessionProjectionAction, OpenInChatPanelAction, ToggleAgentStatusAction, ToggleAgentSessionProjectionAction } from './agentSessionProjectionActions.js'; +import { EnterAgentSessionProjectionAction, ExitAgentSessionProjectionAction, ToggleAgentStatusAction, ToggleAgentSessionProjectionAction } from './agentSessionProjectionActions.js'; import { IAgentStatusService, AgentStatusService } from './agentStatusService.js'; import { AgentStatusWidget } from './agentStatusWidget.js'; import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js'; @@ -52,14 +52,16 @@ registerAction2(FindAgentSessionInViewerAction); registerAction2(ShowAgentSessionsSidebar); registerAction2(HideAgentSessionsSidebar); registerAction2(ToggleAgentSessionsSidebar); -registerAction2(ToggleChatViewSessionsAction); +registerAction2(ShowAllAgentSessionsAction); +registerAction2(ShowRecentAgentSessionsAction); +registerAction2(HideAgentSessionsAction); registerAction2(SetAgentSessionsOrientationStackedAction); registerAction2(SetAgentSessionsOrientationSideBySideAction); // Agent Session Projection registerAction2(EnterAgentSessionProjectionAction); registerAction2(ExitAgentSessionProjectionAction); -registerAction2(OpenInChatPanelAction); +// registerAction2(OpenInChatPanelAction); // TODO@joshspicer https://github.com/microsoft/vscode/issues/288082 registerAction2(ToggleAgentStatusAction); registerAction2(ToggleAgentSessionProjectionAction); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts index bc34a7563600a..5537672599a5d 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts @@ -36,18 +36,29 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; //#region Chat View -export class ToggleChatViewSessionsAction extends Action2 { +const showSessionsSubmenu = new MenuId('chatShowSessionsSubmenu'); +MenuRegistry.appendMenuItem(MenuId.ChatWelcomeContext, { + submenu: showSessionsSubmenu, + title: localize2('chat.showSessions', "Show Sessions"), + group: '0_sessions', + order: 1, + when: ChatContextKeys.inChatEditor.negate() +}); + +export class ShowAllAgentSessionsAction extends Action2 { constructor() { super({ - id: 'workbench.action.chat.toggleChatViewSessions', - title: localize2('chat.toggleChatViewSessions.label', "Show Sessions"), - toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true), + id: 'workbench.action.chat.showAllAgentSessions', + title: localize2('chat.showSessions.all', "All"), + toggled: ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true), + ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowRecentOnly}`, false) + ), menu: { - id: MenuId.ChatWelcomeContext, - group: '0_sessions', - order: 1, - when: ChatContextKeys.inChatEditor.negate() + id: showSessionsSubmenu, + group: 'navigation', + order: 1 } }); } @@ -55,8 +66,56 @@ export class ToggleChatViewSessionsAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); - const chatViewSessionsEnabled = configurationService.getValue(ChatConfiguration.ChatViewSessionsEnabled); - await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, !chatViewSessionsEnabled); + await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true); + await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowRecentOnly, false); + } +} + +export class ShowRecentAgentSessionsAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.chat.showRecentAgentSessions', + title: localize2('chat.showSessions.recent', "Recent"), + toggled: ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true), + ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowRecentOnly}`, true) + ), + menu: { + id: showSessionsSubmenu, + group: 'navigation', + order: 2 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + + await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true); + await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowRecentOnly, true); + } +} + +export class HideAgentSessionsAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.chat.hideAgentSessions', + title: localize2('chat.showSessions.none', "None"), + toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, false), + menu: { + id: showSessionsSubmenu, + group: 'navigation', + order: 3 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + + await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, false); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 968eacb5deae7..c4c1011d90486 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -196,7 +196,7 @@ configurationRegistry.registerConfiguration({ [ChatConfiguration.AgentStatusEnabled]: { type: 'boolean', markdownDescription: nls.localize('chat.agentsControl.enabled', "Controls whether the Agent Status is shown in the title bar command center, replacing the search box with quick access to chat sessions."), - default: true, + default: false, tags: ['experimental'] }, [ChatConfiguration.AgentSessionProjectionEnabled]: { @@ -387,6 +387,11 @@ configurationRegistry.registerConfiguration({ default: true, description: nls.localize('chat.viewSessions.enabled', "Show chat agent sessions when chat is empty or to the side when chat view is wide enough."), }, + [ChatConfiguration.ChatViewSessionsShowRecentOnly]: { + type: 'boolean', + default: false, + description: nls.localize('chat.viewSessions.showRecentOnly', "When enabled, only show recent sessions in the stacked sessions view. When disabled, show all sessions."), + }, [ChatConfiguration.ChatViewSessionsOrientation]: { type: 'string', enum: ['stacked', 'sideBySide'], diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css index c70f5b6ba08a4..5de78f83dc1a8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css @@ -122,17 +122,17 @@ flex: 0 1 auto; } -.models-widget .models-table-container .monaco-table-tr.models-status-row .monaco-table-td .model-name-container .model-name .monaco-highlighted-label.error-status { +.models-widget .models-table-container .monaco-list-row:not(.selected) .monaco-table-tr.models-status-row .monaco-table-td .model-name-container .model-name .monaco-highlighted-label.error-status { color: var(--vscode-errorForeground); } -.models-widget .models-table-container .monaco-table-tr.models-status-row .monaco-table-td .model-name-container .model-name .monaco-highlighted-label.warning-status { +.models-widget .models-table-container .monaco-list-row:not(.selected) .monaco-table-tr.models-status-row .monaco-table-td .model-name-container .model-name .monaco-highlighted-label.warning-status { color: var(--vscode-editorWarning-foreground); } /** Actions column styling **/ -.models-widget .models-table-container .monaco-table-tr.models-model-row.model-hidden .models-table-column.models-actions-column { +.models-widget .models-table-container .monaco-list-row .monaco-table-tr.models-model-row.model-hidden .models-table-column.models-actions-column { opacity: 1; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts index 3fb5d98607c7b..7b973bc922dc8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts @@ -1158,7 +1158,11 @@ async function openChatSession(accessor: ServicesAccessor, openOptions: NewChatS switch (openOptions.position) { case ChatSessionPosition.Sidebar: { const view = await viewsService.openView(ChatViewId) as ChatViewPane; - await view.loadSession(resource); + if (openOptions.type === AgentSessionProviders.Local) { + await view.widget.clear(); + } else { + await view.loadSession(resource); + } view.focus(); break; } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatReferencesContentPart.ts index 0885f4e78fdad..8e0123937dbfb 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatReferencesContentPart.ts @@ -57,15 +57,7 @@ export interface IChatReferenceListItem extends IChatContentReference { excluded?: boolean; } -export interface IChatListDividerItem { - kind: 'divider'; - label: string; - menuId?: MenuId; - menuArg?: unknown; - scopedInstantiationService?: IInstantiationService; -} - -export type IChatCollapsibleListItem = IChatReferenceListItem | IChatWarningMessage | IChatListDividerItem; +export type IChatCollapsibleListItem = IChatReferenceListItem | IChatWarningMessage; export class ChatCollapsibleListContentPart extends ChatCollapsibleContentPart { @@ -218,7 +210,7 @@ export class CollapsibleListPool extends Disposable { 'ChatListRenderer', container, new CollapsibleListDelegate(), - [this.instantiationService.createInstance(CollapsibleListRenderer, resourceLabels, this.menuId), this.instantiationService.createInstance(DividerRenderer)], + [this.instantiationService.createInstance(CollapsibleListRenderer, resourceLabels, this.menuId)], { ...this.listOptions, alwaysConsumeMouseWheel: false, @@ -227,9 +219,6 @@ export class CollapsibleListPool extends Disposable { if (element.kind === 'warning') { return element.content.value; } - if (element.kind === 'divider') { - return element.label; - } const reference = element.reference; if (typeof reference === 'string') { return reference; @@ -294,9 +283,6 @@ class CollapsibleListDelegate implements IListVirtualDelegate { - static TEMPLATE_ID = 'chatListDividerRenderer'; - readonly templateId: string = DividerRenderer.TEMPLATE_ID; - - constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { } - - renderTemplate(container: HTMLElement): IDividerTemplate { - const templateDisposables = new DisposableStore(); - const elementDisposables = templateDisposables.add(new DisposableStore()); - container.classList.add('chat-list-divider'); - const label = dom.append(container, dom.$('span.chat-list-divider-label')); - const line = dom.append(container, dom.$('div.chat-list-divider-line')); - const toolbarContainer = dom.append(container, dom.$('.chat-list-divider-toolbar')); - - return { container, label, line, toolbarContainer, templateDisposables, elementDisposables, toolbar: undefined }; - } - - renderElement(data: IChatListDividerItem, index: number, templateData: IDividerTemplate): void { - templateData.label.textContent = data.label; - - // Clear element-specific disposables from previous render - templateData.elementDisposables.clear(); - templateData.toolbar = undefined; - dom.clearNode(templateData.toolbarContainer); - - if (data.menuId) { - const instantiationService = data.scopedInstantiationService || this.instantiationService; - templateData.toolbar = templateData.elementDisposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, templateData.toolbarContainer, data.menuId, { menuOptions: { arg: data.menuArg } })); - } - } - - disposeTemplate(templateData: IDividerTemplate): void { - templateData.templateDisposables.dispose(); - } -} - function getResourceLabelForGithubUri(uri: URI): IResourceLabelProps { const repoPath = uri.path.split('/').slice(1, 3).join('/'); const filePath = uri.path.split('/').slice(5); @@ -558,7 +491,7 @@ function getLineRangeFromGithubUri(uri: URI): IRange | undefined { } function getResourceForElement(element: IChatCollapsibleListItem): URI | null { - if (element.kind === 'warning' || element.kind === 'divider') { + if (element.kind === 'warning') { return null; } const { reference } = element; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index c541a208ec73b..bec0a9ba68e99 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -104,6 +104,10 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS const { title, message, disclaimer, terminalCustomActions } = state.confirmationMessages; + // Use pre-computed confirmation data from runInTerminalTool (cd prefix extraction happens there for localization) + const initialContent = terminalData.confirmation?.commandLine ?? (terminalData.commandLine.toolEdited ?? terminalData.commandLine.original).trimStart(); + const cdPrefix = terminalData.confirmation?.cdPrefix ?? ''; + const autoApproveEnabled = this.configurationService.getValue(TerminalContribSettingId.EnableAutoApprove) === true; const autoApproveWarningAccepted = this.storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); let moreActions: (IChatConfirmationButton | Separator)[] | undefined = undefined; @@ -145,7 +149,6 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS } }; const languageId = this.languageService.getLanguageIdByLanguageName(terminalData.language ?? 'sh') ?? 'shellscript'; - const initialContent = (terminalData.commandLine.toolEdited ?? terminalData.commandLine.original).trimStart(); const model = this._register(this.modelService.createModel( initialContent, this.languageService.createById(languageId), @@ -181,7 +184,12 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS this._register(model.onDidChangeContent(e => { const currentValue = model.getValue(); // Only set userEdited if the content actually differs from the initial value - terminalData.commandLine.userEdited = currentValue !== initialContent ? currentValue : undefined; + // Prepend cd prefix back if it was extracted for display + if (currentValue !== initialContent) { + terminalData.commandLine.userEdited = cdPrefix + currentValue; + } else { + terminalData.commandLine.userEdited = undefined; + } })); const elements = h('.chat-confirmation-message-terminal', [ h('.chat-confirmation-message-terminal-editor@editor'), diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index c170b7da1a58f..1766047413e02 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -84,7 +84,7 @@ import { IChatEditingSession, IModifiedFileEntry, ModifiedFileEntryState } from import { IChatModelInputState, IChatRequestModeInfo, IInputModel } from '../../../common/model/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../../../common/chatModes.js'; import { IChatFollowup, IChatService, IChatSessionContext } from '../../../common/chatService/chatService.js'; -import { IChatSessionFileChange, IChatSessionProviderOptionItem, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; +import { IChatSessionProviderOptionItem, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; import { getChatSessionType } from '../../../common/model/chatUri.js'; import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isStringImplicitContextValue, isStringVariableEntry } from '../../../common/attachments/chatVariableEntries.js'; import { IChatResponseViewModel } from '../../../common/model/chatViewModel.js'; @@ -2302,6 +2302,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } const modifiedEntries = derivedOpts({ equalsFn: arraysEqual }, r => { + // Background chat sessions render the working set based on the session files, and not the editing session + const sessionResource = chatEditingSession?.chatSessionResource ?? this._widget?.viewModel?.model.sessionResource; + if (sessionResource && getChatSessionType(sessionResource) !== localChatSessionType) { + return []; + } + return chatEditingSession?.entries.read(r).filter(entry => entry.state.read(r) === ModifiedFileEntryState.Modified) || []; }); @@ -2368,29 +2374,16 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })) ); - const shouldRender = derived(reader => { - const sessionFilesLength = sessionFiles.read(reader).length; - const editSessionEntriesLength = editSessionEntries.read(reader).length; - - const sessionResource = chatEditingSession?.chatSessionResource ?? this._widget?.viewModel?.model.sessionResource; - if (sessionResource && getChatSessionType(sessionResource) === localChatSessionType) { - return sessionFilesLength > 0 || editSessionEntriesLength > 0; - } - - // For background sessions, only render the - // working set when there are session files - return sessionFilesLength > 0; - }); + const shouldRender = derived(reader => + editSessionEntries.read(reader).length > 0 || sessionFiles.read(reader).length > 0); this._renderingChatEdits.value = autorun(reader => { if (this.options.renderWorkingSet && shouldRender.read(reader)) { this.renderChatEditingSessionWithEntries( reader.store, chatEditingSession, - modifiedEntries, - sessionFileChanges, editSessionEntries, - sessionFiles, + sessionFiles ); } else { dom.clearNode(this.chatEditingSessionWidgetContainer); @@ -2404,10 +2397,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private renderChatEditingSessionWithEntries( store: DisposableStore, chatEditingSession: IChatEditingSession | null, - modifiedEntries: IObservable, - sessionFileChanges: IObservable, - editSessionEntries: IObservable, - sessionEntries: IObservable, + editSessionEntriesObs: IObservable, + sessionEntriesObs: IObservable ) { // Summary of number of files changed // eslint-disable-next-line no-restricted-syntax @@ -2432,7 +2423,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge scopedContextKeyService.createKey(ChatContextKeys.agentSessionType.key, getChatSessionType(sessionResource)); } - this._chatEditsActionsDisposables.add(bindContextKey(ChatContextKeys.hasAgentSessionChanges, scopedContextKeyService, r => !!sessionEntries.read(r)?.length)); + this._chatEditsActionsDisposables.add(bindContextKey(ChatContextKeys.hasAgentSessionChanges, scopedContextKeyService, r => !!sessionEntriesObs.read(r)?.length)); const scopedInstantiationService = this._chatEditsActionsDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService]))); @@ -2447,38 +2438,36 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); const topLevelStats = derived(reader => { - let added = 0; - let removed = 0; - const entries = modifiedEntries.read(reader); - for (const entry of entries) { - if (entry.linesAdded && entry.linesRemoved) { - added += entry.linesAdded.read(reader); - removed += entry.linesRemoved.read(reader); - } - } + const entries = editSessionEntriesObs.read(reader); + const sessionEntries = sessionEntriesObs.read(reader); - let baseLabel = entries.length === 1 ? localize('chatEditingSession.oneFile.1', '1 file changed') : localize('chatEditingSession.manyFiles.1', '{0} files changed', entries.length); - let shouldShowEditingSession = added > 0 || removed > 0; - let topLevelIsSessionMenu = sessionResource && getChatSessionType(sessionResource) !== localChatSessionType; + let added = 0, removed = 0; - if (added === 0 && removed === 0) { - const sessionValue = sessionFileChanges.read(reader) || []; - for (const entry of sessionValue) { - added += entry.insertions; - removed += entry.deletions; + if (entries.length > 0) { + for (const entry of entries) { + if (entry.kind === 'reference' && entry.options?.diffMeta) { + added += entry.options.diffMeta.added; + removed += entry.options.diffMeta.removed; + } + } + } else { + for (const entry of sessionEntries) { + if (entry.kind === 'reference' && entry.options?.diffMeta) { + added += entry.options.diffMeta.added; + removed += entry.options.diffMeta.removed; + } } - - shouldShowEditingSession = sessionValue.length > 0; - baseLabel = sessionValue.length === 1 ? localize('chatEditingSession.oneFile.2', '1 file ready to merge') : localize('chatEditingSession.manyFiles.2', '{0} files ready to merge', sessionValue.length); - topLevelIsSessionMenu = true; } - button.label = baseLabel; + const files = entries.length > 0 ? entries.length : sessionEntries.length; + const topLevelIsSessionMenu = entries.length === 0 && sessionEntries.length > 0; + const shouldShowEditingSession = entries.length > 0 || sessionEntries.length > 0; - return { added, removed, shouldShowEditingSession, baseLabel, topLevelIsSessionMenu }; + return { files, added, removed, shouldShowEditingSession, topLevelIsSessionMenu }; }); const topLevelIsSessionMenu = topLevelStats.map(t => t.topLevelIsSessionMenu); + store.add(autorun(reader => { const isSessionMenu = topLevelIsSessionMenu.read(reader); reader.store.add(scopedInstantiationService.createInstance(MenuWorkbenchButtonBar, actionsContainer, isSessionMenu ? MenuId.ChatEditingSessionChangesToolbar : MenuId.ChatEditingWidgetToolbar, { @@ -2500,12 +2489,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); store.add(autorun(reader => { - const { added, removed, shouldShowEditingSession, baseLabel } = topLevelStats.read(reader); + const { files, added, removed, shouldShowEditingSession } = topLevelStats.read(reader); + + const buttonLabel = files === 1 + ? localize('chatEditingSession.oneFile', '1 file changed') + : localize('chatEditingSession.manyFiles', '{0} files changed', files); + + button.label = buttonLabel; + button.element.setAttribute('aria-label', localize('chatEditingSession.ariaLabelWithCounts', '{0}, {1} lines added, {2} lines removed', buttonLabel, added, removed)); - button.label = baseLabel; this._workingSetLinesAddedSpan.value.textContent = `+${added}`; this._workingSetLinesRemovedSpan.value.textContent = `-${removed}`; - button.element.setAttribute('aria-label', localize('chatEditingSession.ariaLabelWithCounts', '{0}, {1} lines added, {2} lines removed', baseLabel, added, removed)); dom.setVisibility(shouldShowEditingSession, this.chatEditingSessionWidgetContainer); if (!shouldShowEditingSession) { @@ -2585,24 +2579,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } store.add(autorun(reader => { - const editEntries = editSessionEntries.read(reader); - const sessionFileEntries = sessionEntries.read(reader) ?? []; - - // Combine entries with an optional divider - const allEntries: IChatCollapsibleListItem[] = [...editEntries]; - if (sessionFileEntries.length > 0) { - if (editEntries.length > 0) { - // Add divider between edit session entries and session file entries - allEntries.push({ - kind: 'divider', - label: localize('chatEditingSession.allChanges', 'Worktree Changes'), - menuId: MenuId.ChatEditingSessionChangesToolbar, - menuArg: sessionResource, - scopedInstantiationService, - }); - } - allEntries.push(...sessionFileEntries); - } + const editEntries = editSessionEntriesObs.read(reader); + const sessionFileEntries = sessionEntriesObs.read(reader); + + // Combine edit session entries with session file changes. At the moment, we + // we can combine these two arrays since local chat sessions use edit session + // entries, while background chat sessions use session file changes. + const allEntries = editEntries.concat(sessionFileEntries); const maxItemsShown = 6; const itemsShown = Math.min(allEntries.length, maxItemsShown); diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/modePickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/widget/input/modePickerActionItem.ts index 5ab6e8dc9a391..8824d54fc9f46 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/modePickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/modePickerActionItem.ts @@ -36,6 +36,9 @@ export interface IModePickerDelegate { readonly sessionResource: () => URI | undefined; } +// TODO: there should be an icon contributed for built-in modes +const builtinDefaultIcon = Codicon.tasklist; + export class ModePickerActionItem extends ChatInputPickerActionViewItem { constructor( action: MenuItemAction, @@ -49,7 +52,7 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem { @IChatModeService chatModeService: IChatModeService, @IMenuService private readonly menuService: IMenuService, @ICommandService commandService: ICommandService, - @IProductService productService: IProductService + @IProductService private readonly _productService: IProductService ) { // Category definitions const builtInCategory = { label: localize('built-in', "Built-In"), order: 0 }; @@ -95,7 +98,7 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem { return { ...makeAction(mode, currentMode), tooltip: mode.description.get() ?? chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, mode.kind)?.description ?? action.tooltip, - icon: mode.icon.get(), + icon: mode.icon.get() ?? (isModeConsideredBuiltIn(mode, this._productService) ? builtinDefaultIcon : undefined), category: agentModeDisabledViaPolicy ? policyDisabledCategory : customCategory }; }; @@ -108,8 +111,7 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem { const otherBuiltinModes = modes.builtin.filter(mode => mode.id !== ChatMode.Agent.id); const customModes = groupBy( modes.custom, - mode => mode.source?.storage === PromptsStorage.extension && mode.source.extensionId.value === productService.defaultChatAgent?.chatExtensionId && mode.source.type === ExtensionAgentSourceType.contribution ? - 'builtin' : 'custom'); + mode => isModeConsideredBuiltIn(mode, this._productService) ? 'builtin' : 'custom'); const customBuiltinModeActions = customModes.builtin?.map(mode => { const action = makeActionFromCustomMode(mode, currentMode); @@ -156,13 +158,21 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem { protected override renderLabel(element: HTMLElement): IDisposable | null { this.setAriaLabelAttributes(element); - const isDefault = this.delegate.currentMode.get().id === ChatMode.Agent.id; - const state = this.delegate.currentMode.get().label.get(); - const icon = this.delegate.currentMode.get().icon.get(); + const currentMode = this.delegate.currentMode.get(); + const isDefault = currentMode.id === ChatMode.Agent.id; + const state = currentMode.label.get(); + let icon = currentMode.icon.get(); + + // Every built-in mode should have an icon. // TODO: this should be provided by the mode itself + if (!icon && isModeConsideredBuiltIn(currentMode, this._productService)) { + icon = builtinDefaultIcon; + } const labelElements = []; - labelElements.push(...renderLabelWithIcons(`$(${icon.id})`)); - if (!isDefault) { + if (icon) { + labelElements.push(...renderLabelWithIcons(`$(${icon.id})`)); + } + if (!isDefault || !icon) { labelElements.push(dom.$('span.chat-input-picker-label', undefined, state)); } labelElements.push(...renderLabelWithIcons(`$(chevron-down)`)); @@ -171,3 +181,10 @@ export class ModePickerActionItem extends ChatInputPickerActionViewItem { return null; } } + +function isModeConsideredBuiltIn(mode: IChatMode, productService: IProductService): boolean { + if (mode.isBuiltin) { + return true; + } + return mode.source?.storage === PromptsStorage.extension && mode.source.extensionId.value === productService.defaultChatAgent?.chatExtensionId && mode.source.type === ExtensionAgentSourceType.contribution; +} diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css index 69e49066729b3..b28b64bd95001 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -2108,41 +2108,6 @@ have to be updated for changes to the rules above, or to support more deeply nes display: none; } -.interactive-session .chat-list-divider { - display: flex; - align-items: center; - padding: 2px 3px; - font-size: 11px; - color: var(--vscode-descriptionForeground); - gap: 8px; - pointer-events: none; - user-select: none; -} - -.interactive-session .monaco-list .monaco-list-row:has(.chat-list-divider) { - background-color: transparent !important; - cursor: default; -} - -.interactive-session .chat-list-divider .chat-list-divider-label { - text-transform: uppercase; - letter-spacing: 0.04em; - flex-shrink: 0; -} - -.interactive-session .chat-list-divider .chat-list-divider-line { - flex: 1; - height: 1px; - background-color: var(--vscode-editorWidget-border, var(--vscode-contrastBorder)); - opacity: 0.5; -} - -.interactive-session .chat-list-divider .chat-list-divider-toolbar { - display: flex; - align-items: center; - pointer-events: auto; -} - .interactive-session .chat-summary-list .monaco-list .monaco-list-row { border-radius: 4px; } diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts index 0b75f36b5a439..82e01d19a974e 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts @@ -50,7 +50,6 @@ import { ChatWidget } from '../../widget/chatWidget.js'; import { ChatViewWelcomeController, IViewWelcomeDelegate } from '../../viewsWelcome/chatViewWelcomeController.js'; import { IWorkbenchLayoutService, LayoutSettings, Position } from '../../../../../services/layout/browser/layoutService.js'; import { AgentSessionsViewerOrientation, AgentSessionsViewerPosition } from '../../agentSessions/agentSessions.js'; -import { Link } from '../../../../../../platform/opener/browser/link.js'; import { IProgressService } from '../../../../../../platform/progress/common/progress.js'; import { ChatViewId } from '../../chat.js'; import { disposableTimeout } from '../../../../../../base/common/async.js'; @@ -63,7 +62,6 @@ import { IChatEntitlementService } from '../../../../../services/chat/common/cha interface IChatViewPaneState extends Partial { sessionId?: string; - sessionsViewerLimited?: boolean; sessionsSidebarWidth?: number; } @@ -123,7 +121,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { ) { this.viewState.sessionId = undefined; // clear persisted session on fresh start } - this.sessionsViewerLimited = this.viewState.sessionsViewerLimited ?? true; + this.sessionsViewerLimited = this.configurationService.getValue(ChatConfiguration.ChatViewSessionsShowRecentOnly) ?? false; + this.sessionsViewerVisible = false; // will be updated from layout code this.sessionsViewerSidebarWidth = Math.max(ChatViewPane.SESSIONS_SIDEBAR_MIN_WIDTH, this.viewState.sessionsSidebarWidth ?? ChatViewPane.SESSIONS_SIDEBAR_DEFAULT_WIDTH); // Contextkeys @@ -133,22 +132,18 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.sessionsViewerPositionContext = ChatContextKeys.agentSessionsViewerPosition.bindTo(contextKeyService); this.sessionsViewerVisibilityContext = ChatContextKeys.agentSessionsViewerVisible.bindTo(contextKeyService); - this.updateContextKeys(false); + this.updateContextKeys(); this.registerListeners(); } - private updateContextKeys(fromEvent: boolean): void { + private updateContextKeys(): void { const { position, location } = this.getViewPositionAndLocation(); this.sessionsViewerLimitedContext.set(this.sessionsViewerLimited); this.chatViewLocationContext.set(location ?? ViewContainerLocation.AuxiliaryBar); this.sessionsViewerOrientationContext.set(this.sessionsViewerOrientation); this.sessionsViewerPositionContext.set(position === Position.RIGHT ? AgentSessionsViewerPosition.Right : AgentSessionsViewerPosition.Left); - - if (fromEvent && this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); - } } private getViewPositionAndLocation(): { position: Position; location: ViewContainerLocation } { @@ -192,8 +187,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.viewPaneContainer?.classList.toggle('chat-view-position-left', position === Position.LEFT); this.viewPaneContainer?.classList.toggle('chat-view-position-right', position === Position.RIGHT); - if (fromEvent && this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); + if (fromEvent) { + this.relayout(); } } @@ -208,7 +203,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.layoutService.onDidChangePanelPosition, Event.filter(this.viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === this.viewDescriptorService.getViewContainerByViewId(this.id)) )(() => { - this.updateContextKeys(false); + this.updateContextKeys(); this.updateViewPaneClasses(true /* layout here */); })); @@ -217,6 +212,22 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { return e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION); })(() => this.updateViewPaneClasses(true))); + // Sessions viewer limited setting changes + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => { + return e.affectsConfiguration(ChatConfiguration.ChatViewSessionsShowRecentOnly); + })(() => { + const oldSessionsViewerLimited = this.sessionsViewerLimited; + if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) { + this.sessionsViewerLimited = false; // side by side always shows all + } else { + this.sessionsViewerLimited = this.configurationService.getValue(ChatConfiguration.ChatViewSessionsShowRecentOnly) ?? false; + } + + if (oldSessionsViewerLimited !== this.sessionsViewerLimited) { + this.notifySessionsControlLimitedChanged(true /* layout */, true /* update */); + } + })); + // Entitlement changes this._register(this.chatEntitlementService.onDidChangeSentiment(() => { this.updateViewPaneClasses(true); @@ -308,10 +319,9 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { private sessionsTitle: HTMLElement | undefined; private sessionsControlContainer: HTMLElement | undefined; private sessionsControl: AgentSessionsControl | undefined; - private sessionsLinkContainer: HTMLElement | undefined; - private sessionsLink: Link | undefined; private sessionsCount = 0; private sessionsViewerLimited: boolean; + private sessionsViewerVisible: boolean; private sessionsViewerOrientation = AgentSessionsViewerOrientation.Stacked; private sessionsViewerOrientationConfiguration: 'stacked' | 'sideBySide' = 'sideBySide'; private sessionsViewerOrientationContext: IContextKey; @@ -403,22 +413,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { sessionsToolbar.context = sessionsControl; - // Link to Sessions View - this.sessionsLinkContainer = append(sessionsContainer, $('.agent-sessions-link-container')); - this.sessionsLink = this._register(this.instantiationService.createInstance(Link, this.sessionsLinkContainer, { - label: this.sessionsViewerLimited ? localize('showAllSessions', "Show More") : localize('showRecentSessions', "Show Less"), - href: '', - }, { - opener: () => { - this.sessionsViewerLimited = !this.sessionsViewerLimited; - this.viewState.sessionsViewerLimited = this.sessionsViewerLimited; - - this.notifySessionsControlLimitedChanged(true /* layout */, true /* update */); - - sessionsControl.focus(); - } - })); - // Deal with orientation configuration this._register(Event.runAndSubscribe(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ChatConfiguration.ChatViewSessionsOrientation)), e => { const newSessionsViewerOrientationConfiguration = this.configurationService.getValue<'stacked' | 'sideBySide' | unknown>(ChatConfiguration.ChatViewSessionsOrientation); @@ -455,8 +449,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.configurationService.updateValue(ChatConfiguration.ChatViewSessionsOrientation, validatedOrientation); } - if (options.layout && this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); + if (options.layout) { + this.relayout(); } } @@ -465,17 +459,10 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.updateSessionsControlTitle(); - if (this.sessionsLink) { - this.sessionsLink.link = { - label: this.sessionsViewerLimited ? localize('showAllSessions', "Show More") : localize('showRecentSessions', "Show Less"), - href: '' - }; - } - const updatePromise = triggerUpdate ? this.sessionsControl?.update() : undefined; - if (triggerLayout && this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); + if (triggerLayout) { + this.relayout(); } return updatePromise ?? Promise.resolve(); @@ -488,9 +475,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const { changed: visibilityChanged, visible } = this.updateSessionsControlVisibility(); if (visibilityChanged || (countChanged && visible)) { - if (this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); - } + this.relayout(); } } @@ -532,6 +517,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const sessionsContainerVisible = this.sessionsContainer.style.display !== 'none'; setVisibility(newSessionsContainerVisible, this.sessionsContainer); + this.sessionsViewerVisible = newSessionsContainerVisible; this.sessionsViewerVisibilityContext.set(newSessionsContainerVisible); return { @@ -614,9 +600,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { )); this._register(this.titleControl.onDidChangeHeight(() => { - if (this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); - } + this.relayout(); })); } @@ -650,6 +634,18 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { sessionsControl.reveal(sessionResource); } })); + + // When showing sessions stacked, adjust the height of the sessions list to make room for chat input + let lastChatInputHeight: number | undefined; + this._register(chatWidget.input.onDidChangeHeight(() => { + if (this.sessionsViewerVisible && this.sessionsViewerOrientation === AgentSessionsViewerOrientation.Stacked) { + const chatInputHeight = this._widget?.input?.contentHeight; + if (chatInputHeight && chatInputHeight !== lastChatInputHeight) { // ensure we only layout on actual height changes + lastChatInputHeight = chatInputHeight; + this.relayout(); + } + } + })); } private setupContextMenu(parent: HTMLElement): void { @@ -795,7 +791,28 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { //#region Layout + private layoutingBody = false; + + private relayout(): void { + if (this.lastDimensions) { + this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); + } + } + protected override layoutBody(height: number, width: number): void { + if (this.layoutingBody) { + return; // prevent re-entrancy + } + + this.layoutingBody = true; + try { + this.doLayoutBody(height, width); + } finally { + this.layoutingBody = false; + } + } + + private doLayoutBody(height: number, width: number): void { super.layoutBody(height, width); this.lastDimensions = { height, width }; @@ -822,7 +839,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { let heightReduction = 0; let widthReduction = 0; - if (!this.sessionsContainer || !this.sessionsControlContainer || !this.sessionsControl || !this.viewPaneContainer || !this.sessionsTitleContainer || !this.sessionsLinkContainer || !this.sessionsTitle || !this.sessionsLink) { + if (!this.sessionsContainer || !this.sessionsControlContainer || !this.sessionsControl || !this.viewPaneContainer || !this.sessionsTitleContainer || !this.sessionsTitle) { return { heightReduction, widthReduction }; } @@ -866,7 +883,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) { this.sessionsViewerLimited = false; // side by side always shows all } else { - this.sessionsViewerLimited = this.viewState.sessionsViewerLimited ?? true; + this.sessionsViewerLimited = this.configurationService.getValue(ChatConfiguration.ChatViewSessionsShowRecentOnly) ?? false; } let updatePromise: Promise; @@ -904,9 +921,9 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { return { heightReduction: 0, widthReduction: 0 }; } - let availableSessionsHeight = height - this.sessionsTitleContainer.offsetHeight - this.sessionsLinkContainer.offsetHeight; + let availableSessionsHeight = height - this.sessionsTitleContainer.offsetHeight; if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.Stacked) { - availableSessionsHeight -= ChatViewPane.MIN_CHAT_WIDGET_HEIGHT; // always reserve some space for chat input + availableSessionsHeight -= Math.max(ChatViewPane.MIN_CHAT_WIDGET_HEIGHT, this._widget?.input?.contentHeight ?? 0); } // Show as sidebar @@ -996,9 +1013,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.sessionsViewerSidebarWidth = ChatViewPane.SESSIONS_SIDEBAR_DEFAULT_WIDTH; this.viewState.sessionsSidebarWidth = this.sessionsViewerSidebarWidth; - if (this.lastDimensions) { - this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); - } + this.relayout(); })); } diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css index 31fa06e03ee2e..b4eadde9ef239 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css @@ -77,22 +77,6 @@ background-color: var(--vscode-inputOption-activeBackground); } } - - .agent-sessions-link-container { - padding: 8px 0; - font-size: 12px; - text-align: center; - } - - .agent-sessions-link-container a { - color: var(--vscode-descriptionForeground); - } - - .agent-sessions-link-container a:hover, - .agent-sessions-link-container a:active { - text-decoration: none; - color: var(--vscode-textLink-foreground); - } } /* Sessions control: stacked */ @@ -121,11 +105,6 @@ border-left: 1px solid var(--vscode-panel-border); } } - - .agent-sessions-link-container { - /* hide link to show more when side by side */ - display: none; - } } /* diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 97cbfaa63c5b7..b0998c47482f8 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -250,7 +250,7 @@ export interface IChatMode { readonly id: string; readonly name: IObservable; readonly label: IObservable; - readonly icon: IObservable; + readonly icon: IObservable; readonly description: IObservable; readonly isBuiltin: boolean; readonly kind: ChatModeKind; @@ -320,8 +320,8 @@ export class CustomChatMode implements IChatMode { return this._descriptionObservable; } - get icon(): IObservable { - return constObservable(Codicon.tasklist); + get icon(): IObservable { + return constObservable(undefined); } public get isBuiltin(): boolean { diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 7a79e81edc774..5e344ac04c8dc 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -372,6 +372,21 @@ export interface IChatTerminalToolInvocationData { userEdited?: string; toolEdited?: string; }; + /** The working directory URI for the terminal */ + cwd?: UriComponents; + /** + * Pre-computed confirmation display data (localization must happen at source). + * Contains the command line to show in confirmation (potentially without cd prefix) + * and the formatted cwd label if a cd prefix was extracted. + */ + confirmation?: { + /** The command line to display in the confirmation editor */ + commandLine: string; + /** The formatted cwd label to show in title (if cd was extracted) */ + cwdLabel?: string; + /** The cd prefix to prepend back when user edits */ + cdPrefix?: string; + }; /** Message for model recommending the use of an alternative tool */ alternativeRecommendation?: string; language: string; diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 0487a189c3dbb..b441782ef7b1a 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -28,6 +28,7 @@ export enum ChatConfiguration { TodosShowWidget = 'chat.tools.todos.showWidget', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', ChatViewSessionsEnabled = 'chat.viewSessions.enabled', + ChatViewSessionsShowRecentOnly = 'chat.viewSessions.showRecentOnly', ChatViewSessionsOrientation = 'chat.viewSessions.orientation', ChatViewTitleEnabled = 'chat.viewTitle.enabled', SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled', diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index cc16e91151e6b..a4a00dfa7ea59 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -9,7 +9,7 @@ import * as glob from '../../../../../base/common/glob.js'; import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from '../../../../../base/browser/ui/list/list.js'; import { IProgressService, ProgressLocation, } from '../../../../../platform/progress/common/progress.js'; import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; -import { IFileService, FileKind, FileOperationError, FileOperationResult, FileChangeType } from '../../../../../platform/files/common/files.js'; +import { IFileService, FileKind, FileOperationError, FileOperationResult, FileChangeType, FileSystemProviderCapabilities } from '../../../../../platform/files/common/files.js'; import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; @@ -1367,9 +1367,10 @@ export class FilesFilter implements ITreeFilter { const ignoreFile = ignoreTree.get(dirUri); ignoreFile?.updateContents(content.value.toString()); } else { - // Otherwise we create a new ignorefile and add it to the tree + // Otherwise we create a new ignore file and add it to the tree const ignoreParent = ignoreTree.findSubstr(dirUri); - const ignoreFile = new IgnoreFile(content.value.toString(), dirUri.path, ignoreParent); + const ignoreCase = !this.fileService.hasCapability(ignoreFileResource, FileSystemProviderCapabilities.PathCaseSensitive); + const ignoreFile = new IgnoreFile(content.value.toString(), dirUri.path, ignoreParent, ignoreCase); ignoreTree.set(dirUri, ignoreFile); // If we haven't seen this resource before then we need to add it to the list of resources we're tracking if (!this.ignoreFileResourcesPerRoot.get(root)?.has(ignoreFileResource)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 81acecc6588f3..7cbf89ca9f38d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -300,3 +300,35 @@ export function dedupeRules(rules: ICommandApprovalResultWithReason[]): ICommand return array.findIndex(r => isAutoApproveRule(r.rule) && r.rule.sourceText === sourceText) === index; }); } + +export interface IExtractedCdPrefix { + /** The directory path that was extracted from the cd command */ + directory: string; + /** The command to run after the cd */ + command: string; +} + +/** + * Extracts a cd prefix from a command line, returning the directory and remaining command. + * Does not check if the directory matches the current cwd - just extracts the pattern. + */ +export function extractCdPrefix(commandLine: string, shell: string, os: OperatingSystem): IExtractedCdPrefix | undefined { + const isPwsh = isPowerShell(shell, os); + + const cdPrefixMatch = commandLine.match( + isPwsh + ? /^(?:cd(?: \/d)?|Set-Location(?: -Path)?) (?[^\s]+) ?(?:&&|;)\s+(?.+)$/i + : /^cd (?[^\s]+) &&\s+(?.+)$/ + ); + const cdDir = cdPrefixMatch?.groups?.dir; + const cdSuffix = cdPrefixMatch?.groups?.suffix; + if (cdDir && cdSuffix) { + // Remove any surrounding quotes + let cdDirPath = cdDir; + if (cdDirPath.startsWith('"') && cdDirPath.endsWith('"')) { + cdDirPath = cdDirPath.slice(1, -1); + } + return { directory: cdDirPath, command: cdSuffix }; + } + return undefined; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineRewriter/commandLineCdPrefixRewriter.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineRewriter/commandLineCdPrefixRewriter.ts index 9fece148ea251..c123b764b1f41 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineRewriter/commandLineCdPrefixRewriter.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineRewriter/commandLineCdPrefixRewriter.ts @@ -5,7 +5,7 @@ import { Disposable } from '../../../../../../../base/common/lifecycle.js'; import { OperatingSystem } from '../../../../../../../base/common/platform.js'; -import { isPowerShell } from '../../runInTerminalHelpers.js'; +import { extractCdPrefix } from '../../runInTerminalHelpers.js'; import type { ICommandLineRewriter, ICommandLineRewriterOptions, ICommandLineRewriterResult } from './commandLineRewriter.js'; export class CommandLineCdPrefixRewriter extends Disposable implements ICommandLineRewriter { @@ -14,26 +14,13 @@ export class CommandLineCdPrefixRewriter extends Disposable implements ICommandL return undefined; } - const isPwsh = isPowerShell(options.shell, options.os); - // Re-write the command if it starts with `cd && ` or `cd ; ` // to just `` if the directory matches the current terminal's cwd. This simplifies // the result in the chat by removing redundancies that some models like to add. - const cdPrefixMatch = options.commandLine.match( - isPwsh - ? /^(?:cd(?: \/d)?|Set-Location(?: -Path)?) (?[^\s]+) ?(?:&&|;)\s+(?.+)$/i - : /^cd (?[^\s]+) &&\s+(?.+)$/ - ); - const cdDir = cdPrefixMatch?.groups?.dir; - const cdSuffix = cdPrefixMatch?.groups?.suffix; - if (cdDir && cdSuffix) { - // Remove any surrounding quotes - let cdDirPath = cdDir; - if (cdDirPath.startsWith('"') && cdDirPath.endsWith('"')) { - cdDirPath = cdDirPath.slice(1, -1); - } + const extracted = extractCdPrefix(options.commandLine, options.shell, options.os); + if (extracted) { // Normalize trailing slashes - cdDirPath = cdDirPath.replace(/(?:[\\\/])$/, ''); + let cdDirPath = extracted.directory.replace(/(?:[\\\/])$/, ''); let cwdFsPath = options.cwd.fsPath.replace(/(?:[\\\/])$/, ''); // Case-insensitive comparison on Windows if (options.os === OperatingSystem.Windows) { @@ -41,7 +28,7 @@ export class CommandLineCdPrefixRewriter extends Disposable implements ICommandL cwdFsPath = cwdFsPath.toLowerCase(); } if (cdDirPath === cwdFsPath) { - return { rewritten: cdSuffix, reasoning: 'Removed redundant cd command' }; + return { rewritten: extracted.command, reasoning: 'Removed redundant cd command' }; } } return undefined; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts index fdfbcb4308f39..f251ee467d2f3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -12,13 +12,14 @@ import { Event } from '../../../../../../base/common/event.js'; import { MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; -import { basename } from '../../../../../../base/common/path.js'; +import { basename, posix, win32 } from '../../../../../../base/common/path.js'; import { OperatingSystem, OS } from '../../../../../../base/common/platform.js'; import { count } from '../../../../../../base/common/strings.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { localize } from '../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService, type ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ILabelService } from '../../../../../../platform/label/common/label.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService, ITerminalProfile } from '../../../../../../platform/terminal/common/terminal.js'; @@ -36,7 +37,7 @@ import type { ITerminalExecuteStrategy } from '../executeStrategy/executeStrateg import { NoneExecuteStrategy } from '../executeStrategy/noneExecuteStrategy.js'; import { RichExecuteStrategy } from '../executeStrategy/richExecuteStrategy.js'; import { getOutput } from '../outputHelpers.js'; -import { isFish, isPowerShell, isWindowsPowerShell, isZsh } from '../runInTerminalHelpers.js'; +import { extractCdPrefix, isFish, isPowerShell, isWindowsPowerShell, isZsh } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../treeSitterCommandParser.js'; @@ -300,6 +301,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { @IConfigurationService private readonly _configurationService: IConfigurationService, @IHistoryService private readonly _historyService: IHistoryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILabelService private readonly _labelService: ILabelService, @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IStorageService private readonly _storageService: IStorageService, @@ -402,6 +404,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { original: args.command, toolEdited: rewrittenCommand === args.command ? undefined : rewrittenCommand }, + cwd, language, }; @@ -498,10 +501,41 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { toolSpecificData.autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo; } - const confirmationMessages = isFinalAutoApproved ? undefined : { - title: args.isBackground + // Extract cd prefix for display - show directory in title, command suffix in editor + const commandToDisplay = (toolSpecificData.commandLine.toolEdited ?? toolSpecificData.commandLine.original).trimStart(); + const extractedCd = extractCdPrefix(commandToDisplay, shell, os); + let confirmationTitle: string; + if (extractedCd && cwd) { + // Construct the full directory path using the cwd's scheme/authority + const isAbsolutePath = os === OperatingSystem.Windows + ? win32.isAbsolute(extractedCd.directory) + : posix.isAbsolute(extractedCd.directory); + const directoryUri = isAbsolutePath + ? URI.from({ scheme: cwd.scheme, authority: cwd.authority, path: extractedCd.directory }) + : URI.joinPath(cwd, extractedCd.directory); + const directoryLabel = this._labelService.getUriLabel(directoryUri); + const cdPrefix = commandToDisplay.substring(0, commandToDisplay.length - extractedCd.command.length); + + toolSpecificData.confirmation = { + commandLine: extractedCd.command, + cwdLabel: directoryLabel, + cdPrefix, + }; + + confirmationTitle = args.isBackground + ? localize('runInTerminal.background.inDirectory', "Run `{0}` command in `{1}`? (background terminal)", shellType, directoryLabel) + : localize('runInTerminal.inDirectory', "Run `{0}` command in `{1}`?", shellType, directoryLabel); + } else { + toolSpecificData.confirmation = { + commandLine: commandToDisplay, + }; + confirmationTitle = args.isBackground ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) - : localize('runInTerminal', "Run `{0}` command?", shellType), + : localize('runInTerminal', "Run `{0}` command?", shellType); + } + + const confirmationMessages = isFinalAutoApproved ? undefined : { + title: confirmationTitle, message: new MarkdownString(args.explanation), disclaimer, terminalCustomActions: customActions, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalHelpers.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalHelpers.test.ts index 8ad3ae4b9c4c5..f876846529082 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalHelpers.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalHelpers.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ok, strictEqual } from 'assert'; -import { generateAutoApproveActions, TRUNCATION_MESSAGE, dedupeRules, isPowerShell, sanitizeTerminalOutput, truncateOutputKeepingTail } from '../../browser/runInTerminalHelpers.js'; +import { generateAutoApproveActions, TRUNCATION_MESSAGE, dedupeRules, isPowerShell, sanitizeTerminalOutput, truncateOutputKeepingTail, extractCdPrefix } from '../../browser/runInTerminalHelpers.js'; import { OperatingSystem } from '../../../../../../base/common/platform.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { ConfigurationTarget } from '../../../../../../platform/configuration/common/configuration.js'; @@ -464,3 +464,42 @@ suite('generateAutoApproveActions', () => { strictEqual(subCommandAction, undefined, 'Should not suggest approval for already approved commands'); }); }); + +suite('extractCdPrefix', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + suite('Posix', () => { + function t(commandLine: string, expectedDir: string | undefined, expectedCommand: string | undefined) { + const result = extractCdPrefix(commandLine, 'bash', OperatingSystem.Linux); + strictEqual(result?.directory, expectedDir); + strictEqual(result?.command, expectedCommand); + } + + test('should return undefined when no cd prefix', () => t('echo hello', undefined, undefined)); + test('should return undefined when cd has no suffix', () => t('cd /some/path', undefined, undefined)); + test('should extract cd prefix with && separator', () => t('cd /some/path && npm install', '/some/path', 'npm install')); + test('should extract quoted path', () => t('cd "/some/path" && npm install', '/some/path', 'npm install')); + test('should extract complex suffix', () => t('cd /path && npm install && npm test', '/path', 'npm install && npm test')); + + suite('unsupported patterns', () => { + test('should return undefined for path with escaped space', () => t('cd /some/path\ with\ spaces && npm install', undefined, undefined)); + }); + }); + + suite('PowerShell', () => { + function t(commandLine: string, expectedDir: string | undefined, expectedCommand: string | undefined) { + const result = extractCdPrefix(commandLine, 'pwsh', OperatingSystem.Windows); + strictEqual(result?.directory, expectedDir); + strictEqual(result?.command, expectedCommand); + } + + test('should extract cd with ; separator', () => t('cd C:\\path; npm test', 'C:\\path', 'npm test')); + test('should extract cd /d with && separator', () => t('cd /d C:\\path && echo hello', 'C:\\path', 'echo hello')); + test('should extract Set-Location', () => t('Set-Location C:\\path; npm test', 'C:\\path', 'npm test')); + test('should extract Set-Location -Path', () => t('Set-Location -Path C:\\path; npm test', 'C:\\path', 'npm test')); + + suite('unsupported patterns', () => { + test('should return undefined for quoted path with spaces', () => t('cd "C:\\path with spaces"; npm test', undefined, undefined)); + }); + }); +});